微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

C#和Ruby之间的AES加解密

如何解决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 举报,一经查实,本站将立刻删除。