public static ProtectedPrivateKey Protect(PrivateKey privateKey, string passphrase) { var salt = new byte[32]; using RandomNumberGenerator rng = RandomNumberGenerator.Create(); rng.GetBytes(salt); var kdf = new Pbkdf2 <Sha256Digest>(10240, salt, 32); ImmutableArray <byte> derivedKey = kdf.Derive(passphrase); ImmutableArray <byte> encKey = MakeEncryptionKey(derivedKey); var iv = new byte[16]; rng.GetBytes(iv); var cipher = new Aes128Ctr(iv); ImmutableArray <byte> ciphertext = cipher.Encrypt(encKey, privateKey.ByteArray); ImmutableArray <byte> mac = CalculateMac(derivedKey, ciphertext); Address address = privateKey.ToAddress(); return(new ProtectedPrivateKey(address, kdf, mac, cipher, ciphertext)); }
/// <summary> /// Loads a <see cref="ProtectedPrivateKey"/> from a JSON, according to Ethereum's /// <a href="https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition">Web3 /// Secret Storage Definition</a>. /// </summary> /// <param name="json">A JSON string that encodes a <see cref="ProtectedPrivateKey"/>. /// </param> /// <returns>A protected private key loaded from the given <paramref name="json"/>. /// </returns> /// <exception cref="JsonException">Thrown when the given <paramref name="json"/> is not /// a valid JSON.</exception> /// <exception cref="InvalidKeyJsonException">Thrown when the given key data lacks some /// required fields or consists of wrong types.</exception> /// <exception cref="UnsupportedKeyJsonException">Thrown when the given key data depends on /// an unsupported features (e.g., KDF).</exception> public static ProtectedPrivateKey FromJson(string json) { var options = new JsonDocumentOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip, }; using JsonDocument doc = JsonDocument.Parse(json, options); JsonElement rootElement = doc.RootElement; if (rootElement.ValueKind != JsonValueKind.Object) { throw new InvalidKeyJsonException( "The root of the key JSON must be an object, but it is a/an " + $"{rootElement.ValueKind}." ); } if (!rootElement.TryGetProperty("version", out JsonElement versionElement)) { throw new InvalidKeyJsonException( "The key JSON must contain \"version\" field, but it lacks." ); } if (versionElement.ValueKind != JsonValueKind.Number || !versionElement.TryGetDecimal(out decimal versionNum)) { throw new InvalidKeyJsonException("The \"version\" field must be a number."); } else if (versionNum != 3) { throw new UnsupportedKeyJsonException( $"The key JSON format version {versionNum} is unsupported; " + "Only version 3 is supported." ); } string GetStringProperty(JsonElement element, string fieldName) { if (!element.TryGetProperty(fieldName, out JsonElement fieldElement)) { throw new InvalidKeyJsonException( $"The key JSON must contain \"{fieldName}\" field, but it lacks." ); } string str; try { str = fieldElement.GetString(); } catch (InvalidOperationException) { throw new InvalidKeyJsonException( $"The \"{fieldName}\" field must be a string." ); } if (str is null) { throw new InvalidKeyJsonException( $"The \"{fieldName}\" field must not be null, but a string." ); } return(str); } JsonElement GetObjectProperty(JsonElement element, string fieldName) { if (!element.TryGetProperty(fieldName, out var fieldElement)) { throw new InvalidKeyJsonException( $"The key JSON must contain \"{fieldName}\" field, but it lacks." ); } else if (fieldElement.ValueKind != JsonValueKind.Object) { throw new InvalidKeyJsonException( $"The \"{fieldName}\" field must be an object, but it is a/an " + $"{fieldElement.ValueKind}." ); } return(fieldElement); } byte[] GetHexProperty(JsonElement element, string fieldName) { string str = GetStringProperty(element, fieldName); byte[] bytes; try { bytes = ByteUtil.ParseHex(str); } catch (Exception e) { throw new InvalidKeyJsonException( $"The \"{fieldName}\" field must be a hexadecimal string.\n{e}" ); } return(bytes); } JsonElement crypto = GetObjectProperty(rootElement, "crypto"); string cipherType = GetStringProperty(crypto, "cipher"); JsonElement cipherParamsElement = GetObjectProperty(crypto, "cipherparams"); byte[] ciphertext = GetHexProperty(crypto, "ciphertext"); byte[] mac = GetHexProperty(crypto, "mac"); string kdfType = GetStringProperty(crypto, "kdf"); JsonElement kdfParamsElement = GetObjectProperty(crypto, "kdfparams"); byte[] addressBytes = GetHexProperty(rootElement, "address"); Address address; try { address = new Address(addressBytes); } catch (ArgumentException e) { throw new InvalidKeyJsonException( "The \"address\" field must contain an Ethereum-style address which " + "consists of 40 hexadecimal letters: " + e ); } var cipher = cipherType switch { "aes-128-ctr" => Aes128Ctr.FromJson(cipherParamsElement), _ => throw new UnsupportedKeyJsonException( $"Unsupported cipher type: \"{cipherType}\".") }; IKdf kdf; try { kdf = kdfType switch { "pbkdf2" => Pbkdf2.FromJson(kdfParamsElement), "scrypt" => Scrypt.FromJson(kdfParamsElement), _ => throw new UnsupportedKeyJsonException( $"Unsupported cipher type: \"{kdfType}\".") }; } catch (ArgumentException e) { throw new InvalidKeyJsonException(e.Message); } return(new ProtectedPrivateKey(address, kdf, mac, cipher, ciphertext)); }
public void Activate(string filePath, int type, object[] values) { switch ((CXIActivation)type) { case CXIActivation.RomFS: case CXIActivation.ExeFS: var isRom = (CXIActivation)type == CXIActivation.RomFS; var saveFileDialog = new SaveFileDialog() { Filter = "Binary files (*.bin)|*.bin"}; if (saveFileDialog.ShowDialog() == DialogResult.OK) { var strKey = InputBox.ShowDialog("Please Enter Key:\nPress OK with empty key to save encrypted"); if (strKey != null) //Cancel wasn't pressed { // returns (null if error, byte[0] on Empty, byte[16] on valid) var key = StringUtil.ParseKeyStringToByteArray(strKey); if (key == null) MessageBox.Show(@"Error parsing key string (must be a multiple of 2 and made of hex letters)."); else { var infs = File.OpenRead(filePath); infs.Seek((OffsetInNCSD + (isRom ? Header.RomFSOffset :Header.ExeFSOffset)) * 0x200, SeekOrigin.Begin); var buffer = new byte[(isRom ? Header.RomFSLength : Header.ExeFSLength) * 0x200]; infs.Read(buffer, 0, buffer.Length); infs.Close(); if (key.Length > 0) { var iv = new byte[0x10]; for (var i = 0; i < 8; i++) iv[i] = 0; Buffer.BlockCopy(Header.ProgramID, 0, iv, 8, 8); //TODO: change to TitleID var aes = new Aes128Ctr(key,iv); aes.TransformBlock(buffer); } var outpath = saveFileDialog.FileName; var outfs = File.OpenWrite(outpath); outfs.Write(buffer, 0, buffer.Length); outfs.Close(); } } } break; case CXIActivation.ExHeader: saveFileDialog = new SaveFileDialog() { Filter = "Binary files (*.bin)|*.bin" }; if (saveFileDialog.ShowDialog() == DialogResult.OK) { var strKey = InputBox.ShowDialog("Please Enter Key:\nPress OK with empty key to save encrypted"); if (strKey != null) //Cancel wasn't pressed { // returns (null if error, byte[0] on Empty, byte[16] on valid) var key = StringUtil.ParseKeyStringToByteArray(strKey); if (key == null) MessageBox.Show(@"Error parsing key string (must be a multiple of 2 and made of hex letters)."); else { var infs = File.OpenRead(filePath); infs.Seek(OffsetInNCSD + Marshal.SizeOf(Header), SeekOrigin.Begin); //right after the header var buffer = new byte[Header.ExtendedHeaderSize]; infs.Read(buffer, 0, buffer.Length); infs.Close(); if (key.Length > 0) { var iv = new byte[0x10]; for (var i = 0; i < 8; i++) iv[i] = 0; Buffer.BlockCopy(Header.ProgramID, 0, iv, 8, 8); //TODO: change to TitleID var aes = new Aes128Ctr(key, iv); aes.TransformBlock(buffer); } var outpath = saveFileDialog.FileName; var outfs = File.OpenWrite(outpath); outfs.Write(buffer, 0, buffer.Length); outfs.Close(); } } } break; case CXIActivation.Logo: saveFileDialog = new SaveFileDialog() { Filter = "Binary files (*.bin)|*.bin" }; if (saveFileDialog.ShowDialog() == DialogResult.OK) { var infs = File.OpenRead(filePath); infs.Seek(OffsetInNCSD + (Header.LogoRegionOffset * NcchInfo.media_unit), SeekOrigin.Begin); //right after the header var buffer = new byte[Header.LogoRegionLength * NcchInfo.media_unit]; infs.Read(buffer, 0, buffer.Length); var outpath = saveFileDialog.FileName; var outfs = File.OpenWrite(outpath); outfs.Write(buffer, 0, buffer.Length); outfs.Close(); } break; } }
public void Activate(string filePath, int type, object[] values) { switch ((CXIActivation)type) { case CXIActivation.RomFS: case CXIActivation.ExeFS: var isRom = (CXIActivation)type == CXIActivation.RomFS; var saveFileDialog = new SaveFileDialog() { Filter = "Binary files (*.bin)|*.bin" }; if (saveFileDialog.ShowDialog() == DialogResult.OK) { var strKey = InputBox.ShowDialog("Please Enter Key:\nPress OK with empty key to save encrypted"); if (strKey != null) //Cancel wasn't pressed { // returns (null if error, byte[0] on Empty, byte[16] on valid) var key = StringUtil.ParseKeyStringToByteArray(strKey); if (key == null) { MessageBox.Show(@"Error parsing key string (must be a multiple of 2 and made of hex letters)."); } else { var infs = File.OpenRead(filePath); infs.Seek((OffsetInNCSD + (isRom ? Header.RomFSOffset :Header.ExeFSOffset)) * 0x200, SeekOrigin.Begin); var buffer = new byte[(isRom ? Header.RomFSLength : Header.ExeFSLength) * 0x200]; infs.Read(buffer, 0, buffer.Length); infs.Close(); if (key.Length > 0) { var iv = new byte[0x10]; for (var i = 0; i < 8; i++) { iv[i] = 0; } Buffer.BlockCopy(Header.ProgramID, 0, iv, 8, 8); //TODO: change to TitleID var aes = new Aes128Ctr(key, iv); aes.TransformBlock(buffer); } var outpath = saveFileDialog.FileName; var outfs = File.OpenWrite(outpath); outfs.Write(buffer, 0, buffer.Length); outfs.Close(); } } } break; case CXIActivation.ExHeader: saveFileDialog = new SaveFileDialog() { Filter = "Binary files (*.bin)|*.bin" }; if (saveFileDialog.ShowDialog() == DialogResult.OK) { var strKey = InputBox.ShowDialog("Please Enter Key:\nPress OK with empty key to save encrypted"); if (strKey != null) //Cancel wasn't pressed { // returns (null if error, byte[0] on Empty, byte[16] on valid) var key = StringUtil.ParseKeyStringToByteArray(strKey); if (key == null) { MessageBox.Show(@"Error parsing key string (must be a multiple of 2 and made of hex letters)."); } else { var infs = File.OpenRead(filePath); infs.Seek(OffsetInNCSD + Marshal.SizeOf(Header), SeekOrigin.Begin); //right after the header var buffer = new byte[Header.ExtendedHeaderSize]; infs.Read(buffer, 0, buffer.Length); infs.Close(); if (key.Length > 0) { var iv = new byte[0x10]; for (var i = 0; i < 8; i++) { iv[i] = 0; } Buffer.BlockCopy(Header.ProgramID, 0, iv, 8, 8); //TODO: change to TitleID var aes = new Aes128Ctr(key, iv); aes.TransformBlock(buffer); } var outpath = saveFileDialog.FileName; var outfs = File.OpenWrite(outpath); outfs.Write(buffer, 0, buffer.Length); outfs.Close(); } } } break; case CXIActivation.Logo: saveFileDialog = new SaveFileDialog() { Filter = "Binary files (*.bin)|*.bin" }; if (saveFileDialog.ShowDialog() == DialogResult.OK) { var infs = File.OpenRead(filePath); infs.Seek(OffsetInNCSD + (Header.LogoRegionOffset * NcchInfo.media_unit), SeekOrigin.Begin); //right after the header var buffer = new byte[Header.LogoRegionLength * NcchInfo.media_unit]; infs.Read(buffer, 0, buffer.Length); var outpath = saveFileDialog.FileName; var outfs = File.OpenWrite(outpath); outfs.Write(buffer, 0, buffer.Length); outfs.Close(); } break; } }