/// <summary> /// Load a public key into a MinisignPublicKey object. /// </summary> /// <param name="privateKey">A valid private key.</param> /// <param name="password">The password to decrypt the private key.</param> /// <returns>A MinisignPrivateKey object.</returns> /// <exception cref="OverflowException"></exception> /// <exception cref="CorruptPrivateKeyException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentNullException"></exception> public static MinisignPrivateKey LoadPrivateKey(byte[] privateKey, byte[] password) { if (privateKey == null) { throw new ArgumentException("missing privateKey input", nameof(privateKey)); } if (password == null) { throw new ArgumentException("missing password input", nameof(password)); } var minisignPrivateKey = new MinisignPrivateKey { SignatureAlgorithm = ArrayHelpers.SubArray(privateKey, 0, 2), KdfAlgorithm = ArrayHelpers.SubArray(privateKey, 2, 2), ChecksumAlgorithm = ArrayHelpers.SubArray(privateKey, 4, 2), KdfSalt = ArrayHelpers.SubArray(privateKey, 6, 32), KdfOpsLimit = BitConverter.ToInt64(ArrayHelpers.SubArray(privateKey, 38, 8), 0), //currently unused KdfMemLimit = BitConverter.ToInt64(ArrayHelpers.SubArray(privateKey, 46, 8), 0) //currently unused }; if (!minisignPrivateKey.SignatureAlgorithm.SequenceEqual(Encoding.UTF8.GetBytes(Sigalg))) { throw new CorruptPrivateKeyException("bad SignatureAlgorithm"); } if (!minisignPrivateKey.ChecksumAlgorithm.SequenceEqual(Encoding.UTF8.GetBytes(Chkalg))) { throw new CorruptPrivateKeyException("bad ChecksumAlgorithm"); } if (!minisignPrivateKey.KdfAlgorithm.SequenceEqual(Encoding.UTF8.GetBytes(Kdfalg))) { throw new CorruptPrivateKeyException("bad KdfAlgorithm"); } if (minisignPrivateKey.KdfSalt.Length != KeySaltBytes) { throw new CorruptPrivateKeyException("bad KdfSalt length"); } var encryptedKeyData = ArrayHelpers.SubArray(privateKey, 54, 104); var decryptionKey = PasswordHash.ScryptHashBinary(password, minisignPrivateKey.KdfSalt, PasswordHash.Strength.Sensitive, 104); var decryptedKeyData = EncryptionHelpers.Xor(encryptedKeyData, decryptionKey); minisignPrivateKey.KeyId = ArrayHelpers.SubArray(decryptedKeyData, 0, 8); minisignPrivateKey.SecretKey = ArrayHelpers.SubArray(decryptedKeyData, 8, 64); minisignPrivateKey.Checksum = ArrayHelpers.SubArray(decryptedKeyData, 72, 32); if (minisignPrivateKey.KeyId.Length != KeyNumBytes) { throw new CorruptPrivateKeyException("bad KeyId length"); } var calculatedChecksum = GenericHash.Hash( ArrayHelpers.ConcatArrays(minisignPrivateKey.SignatureAlgorithm, minisignPrivateKey.KeyId, minisignPrivateKey.SecretKey), null, 32); if (!ArrayHelpers.ConstantTimeEquals(minisignPrivateKey.Checksum, calculatedChecksum)) { throw new CorruptPrivateKeyException("bad private key checksum"); } // extract the public key from the private key minisignPrivateKey.PublicKey = PublicKeyAuth.ExtractEd25519PublicKeyFromEd25519SecretKey(minisignPrivateKey.SecretKey); return(minisignPrivateKey); }
/// <summary> /// Generate a new Minisign key pair. /// </summary> /// <param name="password">The password to protect the secret key.</param> /// <param name="writeOutputFiles">If false, no files will be written.</param> /// <param name="outputFolder">The folder to write the files (optional).</param> /// <param name="keyPairFileName">The name of the files to write (optional).</param> /// <returns>A MinisignKeyPair object.</returns> /// <exception cref="ArgumentException"></exception> /// <exception cref="OverflowException"></exception> /// <exception cref="DirectoryNotFoundException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="IOException"></exception> /// <exception cref="UnauthorizedAccessException"></exception> /// <exception cref="PathTooLongException"></exception> /// <exception cref="SecurityException"></exception> /// <exception cref="NotSupportedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> public static MinisignKeyPair GenerateKeyPair(string password, bool writeOutputFiles = false, string outputFolder = "", string keyPairFileName = "minisign") { if (string.IsNullOrEmpty(password)) { throw new ArgumentNullException(nameof(password), "password can not be null"); } if (writeOutputFiles) { //validate the outputFolder if (string.IsNullOrEmpty(outputFolder) || !Directory.Exists(outputFolder)) { throw new DirectoryNotFoundException("outputFolder must exist"); } if (outputFolder.IndexOfAny(Path.GetInvalidPathChars()) > -1) { throw new ArgumentException("The given path to the output folder contains invalid characters!"); } //validate the keyPairFileName if (string.IsNullOrEmpty(keyPairFileName)) { throw new ArgumentNullException(nameof(keyPairFileName), "keyPairFileName can not be empty"); } } var minisignKeyPair = new MinisignKeyPair(); var minisignPrivateKey = new MinisignPrivateKey(); var keyPair = PublicKeyAuth.GenerateKeyPair(); var keyId = SodiumCore.GetRandomBytes(KeyNumBytes); var kdfSalt = SodiumCore.GetRandomBytes(32); minisignPrivateKey.PublicKey = keyPair.PublicKey; minisignPrivateKey.KdfSalt = kdfSalt; minisignPrivateKey.SignatureAlgorithm = Encoding.UTF8.GetBytes(Sigalg); minisignPrivateKey.ChecksumAlgorithm = Encoding.UTF8.GetBytes(Chkalg); minisignPrivateKey.KdfAlgorithm = Encoding.UTF8.GetBytes(Kdfalg); minisignPrivateKey.KdfMemLimit = 1073741824; //currently unused minisignPrivateKey.KdfOpsLimit = 33554432; //currently unused var checksum = GenericHash.Hash( ArrayHelpers.ConcatArrays(minisignPrivateKey.SignatureAlgorithm, keyId, keyPair.PrivateKey), null, 32); minisignPrivateKey.KeyId = keyId; minisignPrivateKey.SecretKey = keyPair.PrivateKey; minisignPrivateKey.Checksum = checksum; var dataToProtect = ArrayHelpers.ConcatArrays(keyId, keyPair.PrivateKey, checksum); var encryptionKey = PasswordHash.ScryptHashBinary(Encoding.UTF8.GetBytes(password), minisignPrivateKey.KdfSalt, PasswordHash.Strength.Sensitive, 104); var encryptedKeyData = EncryptionHelpers.Xor(dataToProtect, encryptionKey); // set up the public key var minisignPublicKey = new MinisignPublicKey { KeyId = keyId, PublicKey = keyPair.PublicKey, SignatureAlgorithm = Encoding.UTF8.GetBytes(Sigalg) }; keyPair.Dispose(); if (writeOutputFiles) { var privateKeyOutputFileName = Path.Combine(outputFolder, keyPairFileName + PrivateKeyFileSuffix); var publicKeyOutputFileName = Path.Combine(outputFolder, keyPairFileName + PublicKeyFileSuffix); var binaryPublicKey = ArrayHelpers.ConcatArrays( minisignPublicKey.SignatureAlgorithm, minisignPublicKey.KeyId, minisignPublicKey.PublicKey ); var publicFileContent = new[] { CommentPrefix + "minisign public key " + Utilities.BinaryToHex(minisignPublicKey.KeyId, Utilities.HexFormat.None, Utilities.HexCase.Upper), Convert.ToBase64String(binaryPublicKey) }; var binaryPrivateKey = ArrayHelpers.ConcatArrays( minisignPrivateKey.SignatureAlgorithm, minisignPrivateKey.KdfAlgorithm, minisignPrivateKey.ChecksumAlgorithm, minisignPrivateKey.KdfSalt, BitConverter.GetBytes(minisignPrivateKey.KdfOpsLimit), BitConverter.GetBytes(minisignPrivateKey.KdfMemLimit), encryptedKeyData ); var privateFileContent = new[] { CommentPrefix + PrivateKeyDefaultComment, Convert.ToBase64String(binaryPrivateKey) }; // files will be overwritten! File.WriteAllLines(publicKeyOutputFileName, publicFileContent); File.WriteAllLines(privateKeyOutputFileName, privateFileContent); minisignKeyPair.MinisignPublicKeyFilePath = publicKeyOutputFileName; minisignKeyPair.MinisignPrivateKeyFilePath = privateKeyOutputFileName; } minisignKeyPair.MinisignPublicKey = minisignPublicKey; minisignKeyPair.MinisignPrivateKey = minisignPrivateKey; return(minisignKeyPair); }