2016-04-08 4 views
1

Я использовал код, который был отправлен here для шифрования и расшифровки, написанный @nerdybeardo. Однако при попытке дешифрования я получаю сообщение об ошибке «pad block corrupted».C# BouncyCastle Исключение: поврежден блок блокировки

Encryptor класса выглядит следующим образом, который реализует шифрование, то MAC:

/// <summary> 
/// Encrypt/decrypt + HMAC using BouncyCastle (C# Java port) 
/// </summary> 
/// <typeparam name="TBlockCipher">The type of the block cipher.</typeparam> 
/// <typeparam name="TDigest">The type of the digest.</typeparam> 
/// <see cref="https://stackoverflow.com/a/13511671/119624"/> 
public sealed class Encryptor<TBlockCipher, TDigest> 
    where TBlockCipher : IBlockCipher, new() 
    where TDigest : IDigest, new() 
{ 
    private readonly Encoding encoding; 

    private readonly byte[] key; 

    private IBlockCipher blockCipher; 

    private BufferedBlockCipher cipher; 

    private HMac mac; 

    /// <summary> 
    /// Initializes a new instance of the <see cref="Encryptor{TBlockCipher, TDigest}"/> class. 
    /// </summary> 
    /// <param name="encoding">The encoding.</param> 
    /// <param name="key">The key.</param> 
    /// <param name="macKey">The mac key.</param> 
    public Encryptor(Encoding encoding, byte[] key, byte[] macKey) 
    { 
     this.encoding = encoding; 
     this.key = key; 
     this.Init(key, macKey, new Pkcs7Padding()); 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="Encryptor{TBlockCipher, TDigest}"/> class. 
    /// </summary> 
    /// <param name="encoding">The encoding.</param> 
    /// <param name="key">The key.</param> 
    /// <param name="macKey">The mac key.</param> 
    /// <param name="padding">The padding.</param> 
    public Encryptor(Encoding encoding, byte[] key, byte[] macKey, IBlockCipherPadding padding) 
    { 
     this.encoding = encoding; 
     this.key = key; 
     this.Init(key, macKey, padding); 
    } 

    /// <summary> 
    /// Encrypts the specified plain. 
    /// </summary> 
    /// <param name="plain">The plain.</param> 
    /// <returns></returns> 
    public string Encrypt(string plain) 
    { 
     return Convert.ToBase64String(EncryptBytes(plain)); 
    } 

    /// <summary> 
    /// Encrypts the bytes. 
    /// </summary> 
    /// <param name="plain">The plain.</param> 
    /// <returns></returns> 
    public byte[] EncryptBytes(string plain) 
    { 
     byte[] input = this.encoding.GetBytes(plain); 

     var iv = this.GenerateInitializationVector(); 

     var cipher = this.BouncyCastleCrypto(true, input, new ParametersWithIV(new KeyParameter(key), iv)); 
     byte[] message = CombineArrays(iv, cipher); 

     this.mac.Reset(); 
     this.mac.BlockUpdate(message, 0, message.Length); 
     var digest = new byte[this.mac.GetUnderlyingDigest().GetDigestSize()]; 
     this.mac.DoFinal(digest, 0); 

     var result = CombineArrays(digest, message); 
     return result; 
    } 

    /// <summary> 
    /// Decrypts the bytes. 
    /// </summary> 
    /// <param name="bytes">The bytes.</param> 
    /// <returns></returns> 
    /// <exception cref="CryptoException"></exception> 
    public byte[] DecryptBytes(byte[] bytes) 
    { 
     // split the digest into component parts 
     var digest = new byte[this.mac.GetUnderlyingDigest().GetDigestSize()]; 
     var message = new byte[bytes.Length - digest.Length]; 
     var iv = new byte[this.blockCipher.GetBlockSize()]; 
     var cipher = new byte[message.Length - iv.Length]; 

     Buffer.BlockCopy(bytes, 0, digest, 0, digest.Length); 
     Buffer.BlockCopy(bytes, digest.Length, message, 0, message.Length); 
     if (!IsValidHMac(digest, message)) 
     { 
      throw new CryptoException(); 
     } 

     Buffer.BlockCopy(message, 0, iv, 0, iv.Length); 
     Buffer.BlockCopy(message, iv.Length, cipher, 0, cipher.Length); 

     byte[] result = this.BouncyCastleCrypto(false, cipher, new ParametersWithIV(new KeyParameter(key), iv)); 
     return result; 
    } 

    /// <summary> 
    /// Decrypts the specified bytes. 
    /// </summary> 
    /// <param name="bytes">The bytes.</param> 
    /// <returns></returns> 
    public string Decrypt(byte[] bytes) 
    { 
     return this.encoding.GetString(DecryptBytes(bytes)); 
    } 

    /// <summary> 
    /// Decrypts the specified cipher. 
    /// </summary> 
    /// <param name="cipher">The cipher.</param> 
    /// <returns></returns> 
    public string Decrypt(string cipher) 
    { 
     return this.Decrypt(Convert.FromBase64String(cipher)); 
    } 

    /// <summary> 
    /// Combines the arrays. 
    /// </summary> 
    /// <param name="source1">The source1.</param> 
    /// <param name="source2">The source2.</param> 
    /// <returns></returns> 
    private static byte[] CombineArrays(byte[] source1, byte[] source2) 
    { 
     var result = new byte[source1.Length + source2.Length]; 
     Buffer.BlockCopy(source1, 0, result, 0, source1.Length); 
     Buffer.BlockCopy(source2, 0, result, source1.Length, source2.Length); 

     return result; 
    } 

    /// <summary> 
    /// Ares the equal. 
    /// </summary> 
    /// <param name="digest">The digest.</param> 
    /// <param name="computed">The computed.</param> 
    /// <returns></returns> 
    private static bool AreEqual(byte[] digest, byte[] computed) 
    { 
     if (digest.Length != computed.Length) 
     { 
      return false; 
     } 

     var result = 0; 
     for (var i = 0; i < digest.Length; i++) 
     { 
      result |= digest[i]^computed[i]; 
     } 

     return result == 0; 
    } 

    /// <summary> 
    /// Initializes the specified key. 
    /// </summary> 
    /// <param name="key">The key.</param> 
    /// <param name="macKey">The mac key.</param> 
    /// <param name="padding">The padding.</param> 
    private void Init(byte[] key, byte[] macKey, IBlockCipherPadding padding) 
    { 
     this.blockCipher = new CbcBlockCipher(new TBlockCipher()); 
     this.cipher = new PaddedBufferedBlockCipher(this.blockCipher, padding); 
     this.mac = new HMac(new TDigest()); 
     this.mac.Init(new KeyParameter(macKey)); 
    } 

    /// <summary> 
    /// Determines whether [is valid h mac] [the specified digest]. 
    /// </summary> 
    /// <param name="digest">The digest.</param> 
    /// <param name="message">The message.</param> 
    /// <returns></returns> 
    private bool IsValidHMac(byte[] digest, byte[] message) 
    { 
     this.mac.Reset(); 
     this.mac.BlockUpdate(message, 0, message.Length); 
     var computed = new byte[this.mac.GetUnderlyingDigest().GetDigestSize()]; 
     this.mac.DoFinal(computed, 0); 

     return AreEqual(digest, computed); 
    } 

    /// <summary> 
    /// Bouncy Castle Cryptography. 
    /// </summary> 
    /// <param name="forEncrypt">if set to <c>true</c> [for encrypt].</param> 
    /// <param name="input">The input.</param> 
    /// <param name="parameters">The parameters.</param> 
    /// <returns></returns> 
    private byte[] BouncyCastleCrypto(bool forEncrypt, byte[] input, ICipherParameters parameters) 
    { 
     try 
     { 
      cipher.Init(forEncrypt, parameters); 

      return this.cipher.DoFinal(input); 
     } 
     catch (CryptoException) 
     { 
      throw; 
     } 
    } 

    /// <summary> 
    /// Generates the initialization vector. 
    /// </summary> 
    /// <returns></returns> 
    private byte[] GenerateInitializationVector() 
    { 
     using (var provider = new RNGCryptoServiceProvider()) 
     { 
      // 1st block 
      var result = new byte[this.blockCipher.GetBlockSize()]; 
      provider.GetBytes(result); 

      return result; 
     } 
    } 
} 

У меня есть простая обертка для двигателя AES. Это выглядит следующим образом:

public class AesSha256Encryptor 
{ 
    private readonly Encryptor<AesEngine, Sha256Digest> provider; 

    /// <summary> 
    /// Initializes a new instance of the <see cref="AesSha256Encryptor"/> class. 
    /// </summary> 
    /// <param name="key">The key.</param> 
    /// <param name="hmacKey">The HMAC key.</param> 
    public AesSha256Encryptor(byte[] key, byte[] hmacKey) 
    { 
     provider = new Encryptor<AesEngine, Sha256Digest>(Encoding.UTF8, key, hmacKey); 
    } 

    /// <summary> 
    /// Encrypts the specified plain. 
    /// </summary> 
    /// <param name="plain">The plain.</param> 
    /// <returns></returns> 
    public string Encrypt(string plain) 
    { 
     return provider.Encrypt(plain); 
    } 

    /// <summary> 
    /// Decrypts the specified cipher. 
    /// </summary> 
    /// <param name="cipher">The cipher.</param> 
    /// <returns></returns> 
    public string Decrypt(string cipher) 
    { 
     return provider.Decrypt(cipher); 
    } 
} 

Я хотел, чтобы иметь возможность иметь различные соли для каждой строки базы данных, поэтому у меня есть менеджер ключей, который работает так:

public static class EncryptionKeyManager 
{ 
    /// <summary> 
    /// The salt length limit 
    /// </summary> 
    private const int SaltLengthLimit = 32; 

    /// <summary> 
    /// Gets the key record. 
    /// </summary> 
    /// <returns></returns> 
    public static KeyRecord GetKeyRecord() 
    { 
     // get the shared passphrasefrom appsettings 
     var sharedPassphrase = GetSharedPassphrase(); 

     // get the client passphrase from config db to sign 
     var clientPassphrase = GetClientPassphrase(); 

     // generate secure random salt 
     var salt = GetSalt(); 

     // get both the encryption key and hmac key 
     // these will be used for Encrypt-then-Mac 
     var key = GetKeyFromPassphrase(sharedPassphrase, salt); 
     var hmacKey = GetKeyFromPassphrase(clientPassphrase, salt); 

     return new KeyRecord 
     { 
      SharedKey = key, 
      HmacKey = hmacKey, 
      Salt = salt 
     }; 
    } 

    /// <summary> 
    /// Gets the client salt. 
    /// </summary> 
    /// <returns></returns> 
    private static string GetClientPassphrase() 
    { 
     var settingsService = ServiceLocator.Current.GetInstance<ISettingService>(); 
     return settingsService.GetSetting(ConstantConfigSettings.EncryptionSettings.ClientPassphrase, defaultValue: "<removed>"); 
    } 

    /// <summary> 
    /// Gets the shared passphrase. 
    /// </summary> 
    /// <returns></returns> 
    private static string GetSharedPassphrase() 
    { 
     return ConfigurationManager.AppSettings[ConstantConfigSettings.EncryptionSettings.SharedPassphrase] ?? "<removed>"; 
    } 

    /// <summary> 
    /// Gets the key from passphrase. 
    /// </summary> 
    /// <param name="passphrase">The passphrase.</param> 
    /// <param name="salt">The salt.</param> 
    /// <returns></returns> 
    private static byte[] GetKeyFromPassphrase(string passphrase, string salt) 
    { 
     var saltArray = Encoding.UTF8.GetBytes(salt); 
     var rfcKey = new Rfc2898DeriveBytes(passphrase, saltArray, 10000); 

     return rfcKey.GetBytes(32); // for a 256-bit key (32*8=128) 
    } 

    /// <summary> 
    /// Gets the salt from a secure random generator.. 
    /// </summary> 
    /// <param name="maximumSaltLength">Maximum length of the salt.</param> 
    /// <returns></returns> 
    private static string GetSalt(int maximumSaltLength = SaltLengthLimit) 
    { 
     var salt = new byte[maximumSaltLength]; 
     using (var random = new RNGCryptoServiceProvider()) 
     { 
      random.GetNonZeroBytes(salt); 
     } 

     return Convert.ToBase64String(salt); 
    } 
} 

Все привыкает, как это для шифрования:

// get key and salt from 
var keyRecord = EncryptionKeyManager.GetKeyRecord(); 
var aesSha256Encryptor = new AesSha256Encryptor(keyRecord.SharedKey, keyRecord.HmacKey); 

// now encrypt and store, include salt 
entity.AccountNumber = aesSha256Encryptor.Encrypt(accountNumber); 
entity.SortCode = aesSha256Encryptor.Encrypt(sortCode); 
entity.Salt = keyRecord.Salt; 

Когда я хочу, чтобы расшифровать, я следующее:

public static class KeyManager 
{ 
    /// <summary> 
    /// Gets the key from passphrase. 
    /// </summary> 
    /// <param name="passphrase">The passphrase.</param> 
    /// <param name="salt">The salt.</param> 
    /// <returns>A byte array.</returns> 
    public static byte[] GetKeyFromPassphrase(string passphrase, string salt) 
    { 
     var saltArray = Encoding.UTF8.GetBytes(salt); 
     var rfcKey = new Rfc2898DeriveBytes(passphrase, saltArray, 10000); 

     return rfcKey.GetBytes(32); // for a 256-bit key (32*8=128) 
    } 
} 

var passphraseKey = KeyManager.GetKeyFromPassphrase(this.Passphrase, this.Salt); 
var hmacKey = KeyManager.GetKeyFromPassphrase(this.ClientPassphrase, this.Salt); 

var aesSha256Encryptor = new AesSha256Encryptor(passphraseKey, hmacKey); 
var plaintext = aesSha256Encryptor.Decrypt(this.CipherText); 

Это приложение для SAAS. Моя основная идея заключалась в том, чтобы иметь кодовую фразу, которая является основой приложения SAAS, которое используется для шифрования/дешифрования, но также имеет конкретную клиентскую кодовую фразу, которая используется для MAC. Причиной этого было распространение ключей между конечными точками (один в базе данных и один в настройке конфигурации). Соль сохраняется в базе данных, поэтому ее можно использовать для дешифрования с использованием той же соли.

Может ли кто-нибудь увидеть, что я делаю неправильно? Почему я получаю ошибку блока блока?

FYI: Ключевая фраза имеет код XKCD variety «лошадиный аккумулятор с степлером», поэтому у них есть дефис. Я не уверен, что это красная селедка.

Я также не уверен, нужна ли уникальная соль в ряду, или я могу просто написать соль? Это перебор?

Обновление Для всех, кто находит это, ошибка была просто в том, что ключевая фраза, которую я считал используемой, была неправильной. Результатом стала ошибка заполнения.

ответ

1

Непонятно, какой именно код вызывает вашу проблему (я имею в виду, что нет минимального примера, который я мог бы запустить, и посмотреть, что не так), но я построил пример, который правильно расшифровывается без ошибок на основе вашего кода. вы можете посмотреть на него и, вероятно, определить свою ошибку. Я сделал EncryptionKeyManager.GetSharedPassphrase() общедоступным, и он возвращает фиксированную строку horse-battery-stapler-correct. Я сделал EncryptionKeyManager.GetClientPassphrase() и публично, и он исправляет horse-battery.

class Program { 
    static void Main(string[] args) { 
     // get key and salt from 
     var keyRecord = EncryptionKeyManager.GetKeyRecord(); 
     var aesSha256Encryptor = new AesSha256Encryptor(keyRecord.SharedKey, keyRecord.HmacKey); 
     string targetData = "4343423343"; 

     var encrypted = aesSha256Encryptor.Encrypt(targetData); 
     var salt = keyRecord.Salt; 
     var decrypted = Decrypt(encrypted, salt); 
     Debug.Assert(targetData == decrypted); 
     Console.WriteLine(decrypted); 
     Console.ReadKey(); 

    } 

    private static string Decrypt(string data, string salt) { 
     var passphraseKey = KeyManager.GetKeyFromPassphrase(EncryptionKeyManager.GetSharedPassphrase(), salt);    
     var hmacKey = KeyManager.GetKeyFromPassphrase(EncryptionKeyManager.GetClientPassphrase(), salt); 
     var aesSha256Encryptor = new AesSha256Encryptor(passphraseKey, hmacKey); 
     var plaintext = aesSha256Encryptor.Decrypt(data); 
     return plaintext; 
    } 
} 
+0

Спасибо. Я помещал это в небольшое консольное приложение, и, похоже, он работает нормально, но когда я извлекаю фактические данные из базы данных, это не так. Я начинаю задаваться вопросом, не связана ли проблема с повреждением, когда данные записываются в базу данных и затем читаются. Тип данных - текущий «nvarchar (max)», где «varchar (max)», вероятно, будет достаточным для строк с кодировкой base64 (хотя я не уверен, почему это изменило бы ситуацию с помощью «nvarchar»). – Junto

+0

Ну, если вы храните двоичные данные - используйте varbinary column, зачем конвертировать в \ из base64 и хранить в виде строки? Что касается вашей проблемы, сначала проверьте правильность вашего шифрования. Захватите точные зашифрованные данные и соль из базы данных и загрузите их в консольное приложение. Если это хорошо - тогда зашифруйте часть в порядке - добавьте больше протоколирования в ваш процесс расшифровки, чтобы проверить, где именно зашифрованные данные становятся поврежденными. – Evk

+0

, честно говоря, я не верю ORM, который мне нужно использовать, но я могу попробовать. Я только что запустил приложение локально (SQL Server Express), и я сравнил строки base64, прежде чем они будут записаны в базу данных, а затем после. Результаты равны. Есть ли что-либо в отношении сопоставления базы данных, которое может привести к повреждению строк? – Junto

0

Это немного смущает, что вы получаете ошибку обивки, это означает, что макинтош подтвержден или не был проверен перед расшифровкой. Я предполагаю, что, поскольку вы используете две разные фразы, если вы правильно указали пароль Mac, и ваше шифрование отключено, что может иметь смысл.

Проблема с утечкой ошибок, возникающих в результате утечки, предназначена для выбранных атак с зашифрованным текстом, поэтому просто сканирование слишком сложного класса, который вы заимствовали, похоже, что нужно проверить mac, однако я бы по крайней мере дважды проверил, mac, что он не даст вам ошибку заполнения, потому что, если это произойдет, это проблема с тем классом, который вы используете.

Соль на строку минимальна, если вы используете кодовые фразы и не переполняете. Я предпочитаю соль для шифрования, как вы можете видеть в my example.

Другая вещь, о которой вы говорили, распространяла ключи, помещая ее в базу данных и одну в конфиге. Разделение хранилища ключа шифрования с помощью ключа mac не является хорошим способом сделать это, если это ваша цель, но я немного неясен для вашей цели. Если ваше приложение было скомпрометировано в базе данных с помощью SQL-инъекции, можно сказать, что для дешифрования требуется только ключ шифрования, Mac должен просто подтвердить, что шифрованный текст не был подделан. Гораздо лучше зашифровать зашифрованный текст с расшифрованными ключами, хранящимися в базе данных, которые были дешифрованы ключами, хранящимися в конфиге, если вы просто просто хотите расширить хранилище ключей немного больше.