Rails使用AES进行加密,过于复杂

5

我在加密第三方供应商提供的值时遇到了问题。

他们的指示如下:

1) Convert the encryption password to a byte array.
2) Convert the value to be encrypted to a byte array.
3) The entire length of the array is inserted as the first four bytes onto the front 
   of the first block of the resultant byte array before encryption.
4) Encrypt the value using AES with:
        1. 256-bit key size,
        2. 256-bit block size, 
        3. Encryption Mode ECB, and
        4. an EMPTY initialization vector.
5) After encryption, you should now have a byte array that holds the encrypted value. 
6) Convert each byte to a HEX format and string all the HEX values together.
7) The final result is a string of HEX values. This is the final encrypted value to be passed. 
   The length of the final value will always be an even number.

EXAMPLE:
Given the following input values:
plainText: 2017/02/07 22:46
secretKey: ABCD1234FGHI5678
The following string will be produced:
D6281D5BE6CD6E79BB41C039F4DD020FBEC9D290AD631B2598A6DFF55C68AD04

到目前为止,我尝试过以下方法...

plain_text = "2017/02/07 22:46"
secret_key = "ABCD1234FGHI5678"

plain_text_byte_array = plain_text.bytes
plain_text_byte_array.unshift(0).unshift(0).unshift(0).unshift(16) # I found a Java example in their documentation and this is what they do. They prepend their byte array with 16, 0, 0, 0
secret_byte_array = secret_key.bytes
secret_byte_array = secret_byte_array.concat([0, 0, 0,...]) # also from their java example, they append the secret_byte array with 16 0's in order to get its length to 32

cipher = OpenSSL::Cipher::AES256.new(:ECB)
cipher.key = secret_byte_array.pack("C*")
encrypted = cipher.update(plain_text_byte_array.pack("C*")) + cipher.final

p encrypted.unpack("H*").first.to_s.upcase

# Result is: 
#    "84A0E5DCA7D704C41332F86E707DDAC244A1A87C38A906145DE4060D2BC5C8F4"

从上面的结果可以看出,我的结果与实际结果相差较大,实际结果应该是"D6281D5BE6CD6E79BB41C039F4DD020FBEC9D290AD631B2598A6DFF55C68AD04"。

请问是否有人知道我是否遗漏了什么或者做了一些奇怪的事情。他们的说明让我很难理解,所以也许我错过了什么。谢谢任何能提供帮助的人!(我已经尝试了大量不同的变化,但结果都不对)。我只需要一些指导,或者至少有人告诉我,我不是因为无法理解他们的说明而发疯了。


1
为了避免字节拼接,可以采用以下方式:plain_text << "\x00\x00\x00\x10"secret_key << ("\x00" * 16) - tadman
6
那些编写指令的人对于整个密码学领域的理解非常有限。这就像读一份由兔子写的蛋糕食谱。 - Luke Joshua Park
2
更重要的是,使用256位块大小的AES是没有意义的。这个服务提供商实际使用的算法叫做Rijndael - AES是它的子集。你不能使用AES实现得到正确的结果。相反,寻找一个可以使用的Rijndael实现,并将块大小设置为256位。AES的块大小始终为128位。 - Luke Joshua Park
2个回答

5

我成功地复制了他们的结果 - 他们使用的过程非常复杂,远非优雅。 我附上了更详细的步骤说明和我用来实现这一目标的 C# 源代码。

  1. 将密码转换为字节数组。 字节数组必须为32个字节长度,如果密码不够长,则应使用0字节进行右填充。 因此,他们的密码,十六进制编码为4142434431323334464748493536373800000000000000000000000000000000

  2. 将要加密的值转换为字节数组。 这很简单,只需使用UTF-8进行编码。

  3. 在加密之前,将整个数组的长度插入到第一个块的前四个字节中。 这是愚蠢的,没有任何意义,但需要将步骤2中的字节数组长度作为 无符号32位整数 并转换为 小端字节数组。 将其添加到步骤2的数组前缀中。

  4. 使用AES加密值。 不,不要这样做。 使用 Rijndael 加密值,使用256位块大小、256位密钥大小、ECB模式和零填充。

  5. 其余部分很容易,只需将加密结果转换为十六进制。

我用来实现这一目标的 C# 代码如下。 很抱歉我不太懂 Ruby。

    // 1. Convert the encryption password to a byte array.
    byte[] passwordBytesOriginal = Encoding.UTF8.GetBytes("ABCD1234FGHI5678");
    byte[] passwordBytes = new byte[32];
    Array.Copy(passwordBytesOriginal, 0, passwordBytes, 0, passwordBytesOriginal.Length);


    // 2. Convert the value to be encrypted to a byte array.
    byte[] valueBytes = Encoding.UTF8.GetBytes("2017/02/07 22:46");

    // 3. The entire length of the array is inserted as the first four bytes onto the front 
    // of the first block of the resultant byte array before encryption.
    byte[] valueLengthAsBytes = BitConverter.GetBytes((uint)valueBytes.Length);
    byte[] finalPlaintext = new byte[valueBytes.Length + valueLengthAsBytes.Length];
    Array.Copy(valueLengthAsBytes, 0, finalPlaintext, 0, valueLengthAsBytes.Length);
    Array.Copy(valueBytes, 0, finalPlaintext, valueLengthAsBytes.Length, valueBytes.Length);

    // 4. Encrypt the value using AES...
    byte[] ciphertext;
    using (RijndaelManaged rijn = new RijndaelManaged())
    {
        rijn.BlockSize = 256;
        rijn.KeySize = 256;
        rijn.Key = passwordBytes;
        rijn.Mode = CipherMode.ECB;
        rijn.Padding = PaddingMode.Zeros;

        var encryptor = rijn.CreateEncryptor();
        ciphertext = encryptor.TransformFinalBlock(finalPlaintext, 0, finalPlaintext.Length);
    }

    // 5., 6., 7...
    string result = BitConverter.ToString(ciphertext).Replace("-", "").ToUpper();
    Console.WriteLine(result); // D6281D5BE6CD6E79BB41C039F4DD020FBEC9D290AD631B2598A6DFF55C68AD04

谢谢你的回答!到目前为止,我在Ruby中实现这个还没有成功。我已经确定了使用加密的库OpenSSL并不使用256位块大小,它只使用256位密钥大小。我一直没有找到一个与RijndaelManaged等效的Ruby库。我相信我必须自己在Ruby中实现Rijndael才能让它在Ruby方面工作。 - Gabriel
1
你总是可以使用另一种语言并读/写标准输入/输出吗?速度有多关键?如果你正在使用Ruby,那么速度应该不会太慢吧? - Luke Joshua Park
谢谢你的建议。我也考虑过这个问题。速度非常重要,但我认为我们可以找到权衡来完成这项任务,以便速度不重要。不过,我认为这条路对我们来说是最后的选择。第三方供应商使用的加密方法已经不再得到支持或安全了,因此我找不到一个库/宝石来完成这个任务。我会和我的团队商量一下,看看我们能做些什么。感谢您的帮助!您的答案帮助我理解了我所做的事情为什么不起作用。 - Gabriel
1
是的,第三方根本不知道他们在做什么。只是为了澄清你的理解,Rijndael本身是一个完全安全的算法 - 他们使用它的方式才是问题(ECB模式,零填充等)。Rijndael肯定得到支持,只是很少使用256位块大小,因为AES非常出名和常见(Rijndael的128位块大小)。 - Luke Joshua Park

4
基于Luke在这里提供的出色答案,这是Ruby版本。我必须使用ruby-mcrypt gem,并使用brew install libmcrypt在本地安装mcrypt库。
正如Luke的答案所指出的那样,密钥应该右填充0。这是我的代码:
   plain_text = "2017/02/07 22:46"
   secret_text = "ABCD1234FGHI5678"
   answer = "D6281D5BE6CD6E79BB41C039F4DD020FBEC9D290AD631B2598A6DFF55C68AD04"

   def format_byte_arrays(plain, secret)
     zero_byte_array = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
     length_array = [16, 0, 0, 0]

     plain_bytes = length_array.concat(plain.bytes)
     secret_bytes = secret.bytes.concat(zero_byte_array)

     [plain_bytes, secret_bytes]
   end

   plain_bytes, secret_bytes = format_byte_arrays(plain_text, secret_text)
   final_plain, final_secret = [plain_bytes.pack("C*"), secret_bytes.pack("C*")]

   cipher = Mcrypt.new("rijndael-256", :ecb, final_secret, nil, :zeros)
   encrypted = cipher.encrypt(final_plain)
   result = encrypted.unpack("H*").first.to_s.upcase

结果将会是正确的答案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接