AES 256 文本加密返回不同的值

AES 256 Text Encryption returning different values

我正在尝试复制一种基于我发现的另一种 C# 方法的加密方法。

C#加密方法EncryptText(word, password)调用另一种方法AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)加密明文:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.IO;
using System.Text;

namespace Rextester
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var f = EncryptText("763059", "515t3ma5m15B4d35");//(word, password)
            Console.WriteLine(f);
        }

        public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
        {
            byte[] encryptedBytes = null;
            byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };

            using (MemoryStream ms = new MemoryStream())
            {
                using (RijndaelManaged AES = new RijndaelManaged())
                {
                    AES.KeySize = 256;
                    AES.BlockSize = 128;

                    var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
                    AES.Key = key.GetBytes(AES.KeySize / 8);
                    AES.IV = key.GetBytes(AES.BlockSize / 8);

                    AES.Mode = CipherMode.CBC;

                    using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
                        cs.Close();
                    }
                    encryptedBytes = ms.ToArray();
                }
            }

            return encryptedBytes;
        }

        public static string EncryptText(string input, string password)
        {
            byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(input);
            byte[] passwordBytes = Encoding.UTF8.GetBytes(password);

            passwordBytes = SHA256.Create().ComputeHash(passwordBytes);

            byte[] bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, passwordBytes);
            string result = Convert.ToBase64String(bytesEncrypted);

            return result;
        }
    }
}

使用单词 763059 和密码 515t3ma5m15B4d35,输出如下:

3cHrXxxL1Djv0K2xW4HuCg==

更新:

现在,我创建了一个 Java Class main,我试图在其中复制以前的代码:

public class main {

    final static String PASSWORD = "515t3ma5m15B4d35";
    final static byte[] SALT = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
    final static int KEY_SIZE = 256;
    final static int BLOCK_SIZE = 128;
    final static int ITERATIONS = 1000;

    public static void main(String[] args) {
        System.out.println(encryptText("763059", PASSWORD));
    }

    public static String encryptText(String word, String password) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(password.getBytes("UTF-8"));
            password = new String(md.digest(), "UTF-8");

            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            KeySpec spec = new PBEKeySpec(password.toCharArray(), SALT, ITERATIONS, KEY_SIZE);
            SecretKey tmp = factory.generateSecret(spec);
            SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES");

            byte[] iv = new byte[BLOCK_SIZE / 8];
            IvParameterSpec ivspec = new IvParameterSpec(iv);
            Cipher ci = Cipher.getInstance("AES/CBC/PKCS5Padding");
            ci.init(Cipher.ENCRYPT_MODE, skey, ivspec);
            byte[] result = ci.doFinal(word.getBytes("UTF-8"));

            return DatatypeConverter.printBase64Binary(result);

        } catch (NoSuchAlgorithmException | UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | InvalidKeySpecException ex) {
            return null;
        }
    }

}

更新:

我在 Java 中阅读了有关使用 256 位密钥的信息,我发现我需要添加 Java Cryptography Extensions 以允许使用 256 个密钥(因为我正在使用 JDK7).

然后我将库添加到项目中,还更改了行:

KeySpec spec = new PBEKeySpec(password.toCharArray(), SALT, ITERATIONS, KEY_SIZE);

键值:

final static int KEY_SIZE = 256;

现在输出如下:

J1xbKOjIeXbQ9njH+67RNw==

我仍然无法实现我的目标。有什么建议吗?

我不是任何 C# 专家,但有几件事需要检查:

阅读有关 Rfc2898DeriveBytes 的文档,我看到该函数正在使用 SHA1 哈希,所以您可以尝试使用 PBKDF2WithHmacSHA1

在这两个实例中(Rfc2898DeriveBytes,PBEKeySpec)你应该确保你的密钥大小是相同的(256 位),这在你的 Java 代码中肯定是错误的

您可以尝试对密钥进行编码和打印,以真正确保它们是相同的。

I need to add Java Cryptography Extensions to allow 256 keys.

取决于您的 JVM 版本。我相信 Oracle JDK 自 v.1.8u162 以来默认包含 Unlimited Strength JCE 策略。如果你使用任何当前的 JRE 版本,你应该没问题

另外:您正在使用(静态)零数组 IV,这是不安全的

最后我决定使用 BouncyCastle API 来使用 RijndaelEngine 的功能,以及使用 PKCS5S2ParametersGenerator 生成 256 位密钥。

我创建了 RijndaelEncryption class 以便能够像在 C# 代码中那样执行加密:

public class RijndaelEncryption {

    public String encryptString(String word, String password, byte[] salt, int iterations, int keySize, int blockSize) {
        try {
            byte[] pswd = sha256String(password, "UTF-8");
            PKCS5S2ParametersGenerator key = keyGeneration(pswd, salt, iterations);
            ParametersWithIV iv = generateIV(key, keySize, blockSize);
            BufferedBlockCipher cipher = getCipher(true, iv);
            byte[] inputText = word.getBytes("UTF-8");
            byte[] newData = new byte[cipher.getOutputSize(inputText.length)];
            int l = cipher.processBytes(inputText, 0, inputText.length, newData, 0);
            cipher.doFinal(newData, l);
            return new String(Base64.encode(newData), "UTF-8");
        } catch (UnsupportedEncodingException | IllegalStateException | DataLengthException | InvalidCipherTextException e) {
            return null;
        }
    }

    public BufferedBlockCipher getCipher(boolean encrypt, ParametersWithIV iv) {
        RijndaelEngine rijndael = new RijndaelEngine();
        BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(rijndael));
        cipher.init(encrypt, iv);
        return cipher;
    }

    public ParametersWithIV generateIV(PKCS5S2ParametersGenerator key, int keySize, int blockSize) {
        try {
            ParametersWithIV iv = null;
            iv = ((ParametersWithIV) key.generateDerivedParameters(keySize, blockSize));
            return iv;
        } catch (Exception e) {
            return null;
        }
    }

    public PKCS5S2ParametersGenerator keyGeneration(byte[] password, byte[] salt, int iterations) {
        try {
            PKCS5S2ParametersGenerator key = new PKCS5S2ParametersGenerator();
            key.init(password, salt, iterations);
            return key;
        } catch (Exception e) {
            return null;
        }
    }

    public byte[] sha256String(String password, Charset charset) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(password.getBytes(charset));
            return md.digest();
        } catch (NoSuchAlgorithmException ex) {
            return null;
        }
    }

    public byte[] sha256String(String password, String charset) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(password.getBytes(charset));
            return md.digest();
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
            return null;
        }
    }
}

并且我在 main 方法中进行了测试:

public static void main(String[] args) {
     RijndaelEncryption s = new RijndaelEncryption();
     byte[] salt = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
     String encryptStr = s.encryptString("763059", "515t3ma5m15B4d35", salt, 1000, 256, 128);
     System.out.println("Encryptation: " + encryptStr);
}

获得:

Encryptation: 3cHrXxxL1Djv0K2xW4HuCg==

我有一个原始问题的答案。供将来没有 bouncycastle 的参考。

你遇到了一些问题。

  1. 密钥大小需要为 256 + 128(还有块大小)
  2. C# 和 Java byte[] 的行为不同,因为 java 字节总是被签名,这会扰乱密码的加密。

这两段代码都作为输出:

xD4R/yvV2tHajUS9p4kqJg==

C#代码:

    using System;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;
    using System.Threading.Tasks;

    namespace tryencryption
    {
        class Program
        {


        static void Main(string[] args)
        {
            var f = EncryptText("yme", "515t3ma5m15B4d35");//(word, password)
            Console.WriteLine(f);
            Console.ReadKey();
        }

        public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, string passwordString)
        {
            byte[] encryptedBytes = null;
            byte[] salt = new byte[] { (byte)0x49, (byte)0x64, (byte)0x76, (byte)0x65, (byte)0x64, (byte)0x65, (byte)0x76, (byte)0x61, (byte)0x6e, (byte)0x20, (byte)0x4d, (byte)0x65, (byte)0x76 };

            using (MemoryStream ms = new MemoryStream())
            {
                using (RijndaelManaged AES = new RijndaelManaged())
                {
                    AES.KeySize = 256;
                    AES.BlockSize = 128;

                    var key = new Rfc2898DeriveBytes(passwordString, salt, 1000);
                    AES.Key = key.GetBytes(AES.KeySize / 8);
                    AES.IV = key.GetBytes(AES.BlockSize / 8);

                    AES.Mode = CipherMode.CBC;

                    using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
                        cs.Close();
                    }
                    encryptedBytes = ms.ToArray();
                }
            }

            return encryptedBytes;
        }

        public static string EncryptText(string input, string password)
        {
            byte[] bytesToBeEncrypted = Encoding.Unicode.GetBytes(input);

            byte[] bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, password);
            string result = Convert.ToBase64String(bytesEncrypted);

            return result;
        }


    }
}

Java 代码(这是来自 android 项目 bcs,这是我的用例,但应该适用于任何地方):

        package com.example.myapplication;

    import androidx.appcompat.app.AppCompatActivity;

    import android.os.Bundle;
    import android.util.Base64;

    import java.nio.charset.StandardCharsets;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.InvalidKeyException;
    import java.security.Key;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.security.spec.AlgorithmParameterSpec;
    import java.security.spec.InvalidKeySpecException;

    import javax.crypto.BadPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.SecretKeyFactory;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.PBEKeySpec;
    import javax.crypto.spec.SecretKeySpec;

    public class MainActivity extends AppCompatActivity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            String result = encrypt("yme", "515t3ma5m15B4d35");
        }


        private static String encrypt(String word, String password) {

            byte[] salt = new byte[] { (byte)0x49, (byte)0x64, (byte)0x76, (byte)0x65, (byte)0x64, (byte)0x65, (byte)0x76, (byte)0x61, (byte)0x6e, (byte)0x20, (byte)0x4d, (byte)0x65, (byte)0x76};

            try {
                SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
                PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, 1000, 256 + 128);
                Key secretKey = factory.generateSecret(pbeKeySpec);
                byte[] test = secretKey.getEncoded();
                byte[] key = new byte[32];
                byte[] iv = new byte[16];
                System.arraycopy(secretKey.getEncoded(), 0, key, 0, 32);
                System.arraycopy(secretKey.getEncoded(), 32, iv, 0, 16);


                SecretKeySpec secret = new SecretKeySpec(key, "AES");
                AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                cipher.init(Cipher.ENCRYPT_MODE, secret, ivSpec);
   //Realise Im using UTF16 here! Maybe you need UTF8
                byte[] plaintextintobytes  =word.getBytes(StandardCharsets.UTF_16LE);
                byte[] encrypted = cipher.doFinal(plaintextintobytes);
                String encryptedInformation = Base64.encodeToString(encrypted, Base64.NO_WRAP);
                return encryptedInformation;

            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeySpecException e) {
                e.printStackTrace();
            } catch (NoSuchPaddingException e) {
                e.printStackTrace();
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            } catch (InvalidAlgorithmParameterException e) {
                e.printStackTrace();
            } catch (IllegalBlockSizeException e) {
                e.printStackTrace();
            } catch (BadPaddingException e) {
                e.printStackTrace();
            }

            return "";
        }


    }