Я использовал код, который был опубликован здесь для шифрования и расшифровки, написанный @nerdybeardo. Однако при попытке дешифрования я получаю сообщение об ошибке "pad block corrupted".
Класс 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="/info/13744/encrypt-and-decrypt-a-string/99267#99267"/>
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-разновидности, то есть стиль "лошадиный аккумулятор-степлер-правильный", поэтому у них есть дефис. я Не уверен, что это красная селедка.
Я также не уверен, нужна ли уникальная соль для каждой строки или я могу просто написать код соли? Это перебор?
Обновление Для тех, кто находит это, ошибка была просто в том, что ключевая фраза, которую я считал используемой, была неправильной. Результатом является ошибка заполнения.