如何解决C#和Ruby之间的AES加解密
我目前正在开发一个项目,我需要将 C# 之间的 AES 加密移植到 Ruby 并提供向后兼容性。虽然它们都独立运行良好,但我在 C# 中加密数据并在 Ruby 中解密数据时遇到了一个问题。
虽然我有一种直觉,认为在 ruby 中将数据转换为字符串的方式可能存在问题,但我不确定这一点,因为我不是该领域的专家(安全)。
有关在 ruby 代码中需要更正哪些内容以在 C# 中解密加密文本的任何指导都会有所帮助。
下面是我的 C# 代码。
public class Encryption
{
private const string SECRET = "readasecret";
static byte[] KEY = new byte[] { 222,11,149,155,122,97,170,8,40,250,67,227,129,147,159,81,108,136,221,41,247,146,114,133,232,31,33,196,130,88,238 };
private static readonly byte[] Salt = Encoding.ASCII.GetBytes("o6MKe324346722kbM7c5");
public static string Encrypt(string nonCrypted)
{
return EncryptStringAES(nonCrypted ?? string.Empty,SECRET);
}
public static string Decrypt(string encrypted)
{
return DecryptStringAES(encrypted,SECRET);
}
private static string EncryptStringAES(string plainText,string sharedSecret)
{
//if (string.IsNullOrEmpty(plainText))
// throw new ArgumentNullException("plainText");
if (string.IsNullOrEmpty(sharedSecret))
throw new ArgumentNullException("sharedSecret");
string outStr; // Encrypted string to return
RijndaelManaged aesAlg = null; // RijndaelManaged object used to encrypt the data.
try
{
// generate the key from the shared SECRET and the salt
var key = new Rfc2898DeriveBytes(sharedSecret,Salt);
// Create a RijndaelManaged object
aesAlg = new RijndaelManaged();
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
// Create a decryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(KEY,aesAlg.IV);
// Create the streams used for encryption.
using (var msEncrypt = new MemoryStream())
{
// prepend the IV
msEncrypt.Write(BitConverter.GetBytes(aesAlg.IV.Length),sizeof(int));
msEncrypt.Write(aesAlg.IV,aesAlg.IV.Length);
using (var csEncrypt = new CryptoStream(msEncrypt,encryptor,CryptoStreamMode.Write))
{
using (var swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
}
outStr = Convert.ToBase64String(msEncrypt.ToArray());
}
}
finally
{
// Clear the RijndaelManaged object.
if (aesAlg != null)
aesAlg.Clear();
}
// Return the encrypted bytes from the memory stream.
return outStr;
}
private static string DecryptStringAES(string cipherText,string sharedSecret)
{
if (string.IsNullOrEmpty(cipherText))
throw new ArgumentNullException("cipherText");
if (string.IsNullOrEmpty(sharedSecret))
throw new ArgumentNullException("sharedSecret");
RijndaelManaged aesAlg = null;
// Declare the string used to hold
// the decrypted text.
string plaintext;
try
{
// generate the key from the shared SECRET and the salt
var key = new Rfc2898DeriveBytes(sharedSecret,Salt);
// Create the streams used for decryption.
byte[] bytes = Convert.FromBase64String(cipherText);
using (var msDecrypt = new MemoryStream(bytes))
{
aesAlg = new RijndaelManaged();
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
// Get the initialization vector from the encrypted stream
aesAlg.IV = ReadByteArray(msDecrypt);
var decryptor = aesAlg.CreateDecryptor(KEY,aesAlg.IV);
using (var csDecrypt = new CryptoStream(msDecrypt,decryptor,CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt))
plaintext = srDecrypt.ReadToEnd();
}
}
}
catch (Exception e)
{
return string.Empty;
}
finally
{
// Clear the RijndaelManaged object.
if (aesAlg != null)
aesAlg.Clear();
}
return plaintext;
}
private static byte[] ReadByteArray(Stream s)
{
var rawLength = new byte[sizeof(int)];
if (s.Read(rawLength,rawLength.Length) != rawLength.Length)
throw new SystemException("Stream did not contain properly formatted byte array");
var buffer = new byte[BitConverter.ToInt32(rawLength,0)];
if (s.Read(buffer,buffer.Length) != buffer.Length)
throw new SystemException("Did not read byte array properly");
return buffer;
}
}
等效的 Ruby 代码如下。
require 'pbkdf2'
require "openssl"
require "base64"
require "encrypted"
require "securerandom"
secret = "readasecret"
salt = "o6MKe324346722kbM7c5"
encrypt_this = "fiskbullsmacka med extra sovs"
rfc_db = PBKDF2.new(password: secret,salt: salt,iterations: 1000,key_length: 32,hash_function: :sha1).bin_string
key = rfc_db.bytes[0,32]
puts key.inspect
cipherkey = key.pack('c*')
# ----------------- ENCRYPTION -------------------------
cipher = Encrypted::Ciph.new("256-128")
cipher.key = cipherkey
cipher.iv = cipher.generate_iv
encrypted_text = cipher.encrypt(encrypt_this)
# Convert string to byte[]
unpackENCString = encrypted_text.unpack("c*")
# Combine IV and data
combEncrypt = cipher.iv.unpack("c*").concat(encrypted_text.unpack("c*"))
# Convert byte[] to string
passingString = combEncrypt.pack("c*")
enc = Base64.encode64(passingString)
puts "Encrypted text :"+enc
# ----------------- DECRYPTION -------------------------
plain = Base64.decode64(enc)
passingbyteArray = plain.unpack("c*")
rfc_db = PBKDF2.new(password: secret,32]
decipherkey = key.pack('c*')
decrypt_this = passingbyteArray[16,passingbyteArray.length() - 16].pack("c*") #from above
decipher = Encrypted::Ciph.new("256-128")
cipher.key = decipherkey #key used above to encrypt
cipher.iv = passingbyteArray[0,16].pack("c*") #initialization vector used above
decrypted_text = cipher.decrypt(decrypt_this)
puts "Decrypted text: "+decrypted_text
解决方法
在发布的 C# 代码中,实现了通过 PBKDF2 的密钥派生,但并未使用。而是应用了硬编码的密钥 KEY
。这可能是出于测试目的。
下面不考虑硬编码密钥,而是考虑派生密钥。为此,在 aesAlg.CreateEncryptor()
和 aesAlg.CreateDecryptor()
中的 C# 代码中,必须传递 KEY
而不是 aesAlg.Key
,之前已将派生密钥分配给该代码。
C# 代码在加密后按顺序连接 IV(到 4 个字节)、IV 和密文的大小。解密时会发生相应的分离。
请注意,实际上没有必要存储 IV 的大小,因为它是已知的:IV 的大小等于块大小,因此 AES 为 16 字节。
在下文中,为简单起见,保留了 IV 大小的串联。
在 Ruby 代码中,使用了各种加密库,尽管 openssl 实际上就足够了。因此,以下实现仅适用于 openssl:
使用 PBKDF2 的密钥推导是:
require "openssl"
require "base64"
# Key derivation (PBKDF2)
secret = "readasecret"
salt = "o6MKe324346722kbM7c5"
key = OpenSSL::KDF.pbkdf2_hmac(secret,salt: salt,iterations: 1000,length: 32,hash: "sha1")
加密是:
# Encryption
plaintext = "fiskbullsmacka med extra sovs"
cipher = OpenSSL::Cipher.new('AES-256-CBC')
cipher.encrypt
cipher.key = key
nonce = cipher.random_iv # random IV
cipher.iv = nonce
ciphertext = cipher.update(plaintext) + cipher.final
# Concatenation
sizeIvCiphertext = ['10000000'].pack('H*').concat(nonce.concat(ciphertext))
sizeIvCiphertextB64 = Base64.encode64(sizeIvCiphertext)
puts sizeIvCiphertextB64 # e.g. EAAAAC40tnEeaRtwutravBiH8vpn4vtjk6s9CAq/XEbyGTGMPwxENInIoAqWlZvR413Aqg==
和解密:
# Separation
sizeIvCiphertext = Base64.decode64(sizeIvCiphertextB64)
size = sizeIvCiphertext[0,4]
iv = sizeIvCiphertext [4,16]
ciphertext = sizeIvCiphertext[4+16,sizeIvCiphertext.length-16]
# Decryption
decipher = OpenSSL::Cipher.new('AES-256-CBC')
decipher.decrypt
decipher.key = key
decipher.iv = iv
decrypted = decipher.update(ciphertext) + decipher.final
puts decrypted # fiskbullsmacka med extra sovs
由此生成的密文可以用C#代码解密。同样,C#代码的密文可以用上面的Ruby代码解密。
请记住,这两个代码都包含一个漏洞。这些代码使用 static 盐进行密钥派生,这是不安全的。相反,应该为每个密钥派生生成一个随机盐。就像 IV 一样,salt 不是秘密的,通常与 IV 和密文一起传递,例如盐 |四 |密文。
此外,对于 PBKDF2,1000 次迭代次数通常太少。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。