/// <summary> /// Unpacks a cipher formerly packed with <see cref="PackHeaderAndCypher(CryptoHeader, byte[])"/>. /// </summary> /// <param name="packedCipher">Packed cipher containing a header and its cipher.</param> /// <param name="header">Receives the extracted header.</param> /// <param name="cipher">Receives the extracted cipher.</param> public static void UnpackHeaderAndCipher(byte[] packedCipher, out CryptoHeader header, out byte[] cipher) { if (packedCipher == null) { throw new ArgumentNullException("packedCipher"); } int lastSeparatorPos = IndexOfLastSeparator(packedCipher, ExpectedSeparatorCount); if (lastSeparatorPos < 0) { throw new CryptoExceptionInvalidCipherFormat(); } // Read cipher part int cipherLength = packedCipher.Length - lastSeparatorPos - 1; cipher = new byte[cipherLength]; Array.Copy(packedCipher, lastSeparatorPos + 1, cipher, 0, cipherLength); // Read header part string headerString = Encoding.UTF8.GetString(packedCipher, 0, lastSeparatorPos + 1); header = UnpackHeader(headerString); }
/// <summary> /// Pack an encryption header and its cipher together to a byte array. /// </summary> /// <param name="header">Header containing the encryption parameters.</param> /// <param name="cipher">Cipher generated with those encryption parameters.</param> /// <returns>Packed cipher.</returns> public static byte[] PackHeaderAndCypher(CryptoHeader header, byte[] cipher) { if (header == null) { throw new ArgumentNullException("header"); } if (cipher == null) { throw new ArgumentNullException("cipher"); } StringBuilder sb = new StringBuilder(); sb.Append(header.AppName); sb.Append(Separator); sb.Append(header.AlgorithmName); sb.Append(Separator); sb.Append(CryptoUtils.BytesToBase64String(header.Nonce)); sb.Append(Separator); sb.Append(header.KdfName); sb.Append(Separator); sb.Append(CryptoUtils.BytesToBase64String(header.Salt)); sb.Append(Separator); sb.Append(header.Cost); sb.Append(Separator); string stringHeader = sb.ToString(); byte[] binaryHeader = CryptoUtils.StringToBytes(stringHeader); byte[] result = new byte[cipher.Length + binaryHeader.Length]; Array.Copy(binaryHeader, 0, result, 0, binaryHeader.Length); Array.Copy(cipher, 0, result, binaryHeader.Length, cipher.Length); return(result); }
/// <inheritdoc/> public byte[] Encrypt( byte[] message, SecureString password, KeyDerivationCostType costType, string encryptorName, string kdfName = Pbkdf2.CryptoKdfName, string compression = null) { if (message == null) { throw new ArgumentNullException(nameof(message)); } ValidatePassword(password); if (_randomSource == null) { throw new ArgumentNullException(nameof(_randomSource)); } if (string.IsNullOrWhiteSpace(encryptorName)) { encryptorName = BouncyCastleXChaCha20.CryptoAlgorithmName; } if (string.IsNullOrWhiteSpace(kdfName)) { encryptorName = Pbkdf2.CryptoKdfName; } ISymmetricEncryptionAlgorithm encryptor = new SymmetricEncryptionAlgorithmFactory().CreateAlgorithm(encryptorName); IKeyDerivationFunction kdf = new KeyDerivationFactory().CreateKdf(kdfName); // Prepare header CryptoHeader header = new CryptoHeader(); header.PackageName = PackageName; header.AlgorithmName = encryptor.Name; header.Nonce = _randomSource.GetRandomBytes(encryptor.ExpectedNonceSize); header.KdfName = kdf.Name; header.Salt = _randomSource.GetRandomBytes(kdf.ExpectedSaltSizeBytes); int cost = kdf.RecommendedCost(costType); header.Cost = cost.ToString(); header.Compression = compression; try { if (string.Equals(CompressionGzip, header.Compression, StringComparison.InvariantCultureIgnoreCase)) { message = CompressUtils.Compress(message); } byte[] key = kdf.DeriveKeyFromPassword(password, encryptor.ExpectedKeySize, header.Salt, cost); byte[] cipher = encryptor.Encrypt(message, key, header.Nonce); return(CryptoHeaderPacker.PackHeaderAndCypher(header, cipher)); } catch (Exception ex) { throw new CryptoException("An unexpected error occured, while encrypting the message.", ex); } }
/// <summary> /// Unpacks a cipher formerly packed with <see cref="PackHeaderAndCypher(CryptoHeader, byte[])"/>. /// </summary> /// <param name="packedCipher">Packed cipher containing a header and its cipher.</param> /// <param name="expectedAppName">The app name which must match.</param> /// <param name="header">Receives the extracted header.</param> /// <param name="cipher">Receives the extracted cipher.</param> /// <exception cref="CryptoExceptionInvalidCipherFormat">Thrown if it doesn't contain a valid header.</exception> /// <exception cref="CryptoUnsupportedRevisionException">Thrown if it was packed with a future incompatible version.</exception> public static void UnpackHeaderAndCipher(byte[] packedCipher, string expectedAppName, out CryptoHeader header, out byte[] cipher) { header = null; cipher = null; if (!HasMatchingHeader(packedCipher, expectedAppName, out int revision)) { throw new CryptoExceptionInvalidCipherFormat(); } if (revision > CryptoHeader.NewestSupportedRevision) { throw new CryptoUnsupportedRevisionException(); } int expectedSeparatorCount = (revision > 1) ? 7 : 6; int lastSeparatorPos = IndexOfLastSeparator(packedCipher, expectedSeparatorCount); if (lastSeparatorPos < 0) { throw new CryptoExceptionInvalidCipherFormat(); } // Read cipher part int cipherLength = packedCipher.Length - lastSeparatorPos - 1; cipher = new byte[cipherLength]; Array.Copy(packedCipher, lastSeparatorPos + 1, cipher, 0, cipherLength); // Read header part try { string headerString = Encoding.UTF8.GetString(packedCipher, 0, lastSeparatorPos + 1); string[] parts = headerString.Split(new char[] { Separator }); header = new CryptoHeader { PackageName = expectedAppName, Revision = revision, AlgorithmName = parts[1], Nonce = CryptoUtils.Base64StringToBytes(parts[2]), KdfName = string.IsNullOrEmpty(parts[3]) ? null : parts[3], Salt = string.IsNullOrEmpty(parts[4]) ? null : CryptoUtils.Base64StringToBytes(parts[4]), Cost = string.IsNullOrEmpty(parts[5]) ? null : parts[5], }; if (revision > 1) { header.Compression = parts[6]; } } catch (Exception) { throw new CryptoExceptionInvalidCipherFormat(); } }
/// <summary> /// Encrypts a message with a user password, and adds a header containing all information /// necessary for the decryption (algorithm, nonce, salt, ...). /// </summary> /// <param name="message">Plain text message to encrypt.</param> /// <param name="password">Password to use for encryption, minimum length is 7 characters.</param> /// <param name="costType">The cost type to use for encryption.</param> /// <param name="randomSource">A cryptographically safe random source.</param> /// <param name="encryptorName">The name of an encryption algorithm which shall be used to /// do the encryption.</param> /// <param name="kdfName">The name of a key derivation function, which can convert the /// password to a key.</param> /// <returns>A binary array containing the cipher.</returns> public byte[] Encrypt( byte[] message, string password, KeyDerivationCostType costType, ICryptoRandomSource randomSource, string encryptorName, string kdfName = Pbkdf2.CryptoKdfName) { if (message == null) { throw new ArgumentNullException("message"); } ValidatePassword(password); if (randomSource == null) { throw new ArgumentNullException("randomSource"); } if (string.IsNullOrWhiteSpace(encryptorName)) { encryptorName = BouncyCastleAesGcm.CryptoAlgorithmName; } if (string.IsNullOrWhiteSpace(kdfName)) { encryptorName = Pbkdf2.CryptoKdfName; } ISymmetricEncryptionAlgorithm encryptor = new SymmetricEncryptionAlgorithmFactory().CreateAlgorithm(encryptorName); IKeyDerivationFunction kdf = new KeyDerivationFactory().CreateKdf(kdfName); // Prepare header CryptoHeader header = new CryptoHeader(); header.AppName = _appName; header.AlgorithmName = encryptor.Name; header.Nonce = randomSource.GetRandomBytes(encryptor.ExpectedNonceSize); header.KdfName = kdf.Name; header.Salt = randomSource.GetRandomBytes(kdf.ExpectedSaltSizeBytes); int cost = kdf.RecommendedCost(costType); header.Cost = cost.ToString(); try { byte[] key = kdf.DeriveKeyFromPassword(password, encryptor.ExpectedKeySize, header.Salt, cost); byte[] cipher = encryptor.Encrypt(message, key, header.Nonce); return(CryptoHeaderPacker.PackHeaderAndCypher(header, cipher)); } catch (Exception ex) { throw new CryptoException("An unexpected error occured, while encrypting the message.", ex); } }
private static CryptoHeader UnpackHeader(string headerString) { string[] parts = headerString.Split(new char[] { Separator }, StringSplitOptions.RemoveEmptyEntries); CryptoHeader result = new CryptoHeader(); result.AppName = parts[0]; result.AlgorithmName = parts[1]; result.Nonce = CryptoUtils.Base64StringToBytes(parts[2]); result.KdfName = parts[3]; result.Salt = CryptoUtils.Base64StringToBytes(parts[4]); result.Cost = parts[5]; return(result); }
/// <inheritdoc/> public byte[] Encrypt( byte[] message, byte[] key, string encryptorName, string compression = null) { if (message == null) { throw new ArgumentNullException(nameof(message)); } if (key == null) { throw new ArgumentNullException(nameof(key)); } if (_randomSource == null) { throw new ArgumentNullException(nameof(_randomSource)); } if (string.IsNullOrWhiteSpace(encryptorName)) { encryptorName = BouncyCastleXChaCha20.CryptoAlgorithmName; } ISymmetricEncryptionAlgorithm encryptor = new SymmetricEncryptionAlgorithmFactory().CreateAlgorithm(encryptorName); // Prepare header CryptoHeader header = new CryptoHeader(); header.PackageName = PackageName; header.AlgorithmName = encryptor.Name; header.Nonce = _randomSource.GetRandomBytes(encryptor.ExpectedNonceSize); header.Compression = compression; try { if (string.Equals(CompressionGzip, header.Compression, StringComparison.InvariantCultureIgnoreCase)) { message = CompressUtils.Compress(message); } byte[] truncatedKey = CryptoUtils.TruncateKey(key, encryptor.ExpectedKeySize); byte[] cipher = encryptor.Encrypt(message, truncatedKey, header.Nonce); return(CryptoHeaderPacker.PackHeaderAndCypher(header, cipher)); } catch (Exception ex) { throw new CryptoException("An unexpected error occured, while encrypting the message.", ex); } }