【问题标题】:encrypt using BouncyCastle (java) and Gcrypt (C) gives different result使用 BouncyCastle (java) 和 Gcrypt (C) 加密给出不同的结果
【发布时间】:2015-06-26 22:13:44
【问题描述】:

我编写了这个简单的 Java 程序,它加密一个字符串并输出 iv、salt、派生密钥和密文的十六进制值。

public class tmp{
    static Cipher encryptionCipher;
    static String RANDOM_ALGORITHM = "SHA1PRNG";
    static String PBE_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
    static String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";
    static String SECRET_KEY_ALGORITHM = "AES";
    static int PBE_ITERATION_COUNT = 2048;
    static String PROVIDER = "BC";

    public static byte[] generateIv() {
        try{
        SecureRandom random;
        random = SecureRandom.getInstance(RANDOM_ALGORITHM);
        byte[] iv = new byte[16];
        random.nextBytes(iv);
        return iv;
        } catch(Exception e){
        return null;            // Always must return something
        }
    }

    public static byte[] generateSalt() {
         try {SecureRandom random;
         random = SecureRandom.getInstance(RANDOM_ALGORITHM);
         byte[] salt = new byte[32];
         random.nextBytes(salt);
         return salt;
         } catch(Exception e){
        return null;            // Always must return something
    }
     }

     public static SecretKey getSecretKey(String password, byte[] salt){
         try {
            PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, PBE_ITERATION_COUNT, 256);
            SecretKeyFactory factory = SecretKeyFactory.getInstance(PBE_ALGORITHM, PROVIDER);
            SecretKey tmp = factory.generateSecret(pbeKeySpec);
            return new SecretKeySpec(tmp.getEncoded(), SECRET_KEY_ALGORITHM);
        } catch(Exception e){
        System.out.println(e);            // Always must return something
        return null;
        }        
     }

     public static String encrypt(String plaintext, Key key, byte[] iv) {
        try {
            AlgorithmParameterSpec ivParamSpec = new IvParameterSpec(iv);
            encryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
            encryptionCipher.init(Cipher.ENCRYPT_MODE, key, ivParamSpec);
            byte[] ciphertext = encryptionCipher.doFinal(plaintext.getBytes("UTF-8"));
            String cipherHexString = DatatypeConverter.printHexBinary(ciphertext);
            return cipherHexString;
        }
        catch (Exception e) {
             System.out.println(e);
            return null;
        }

    }

     public static void main (String[] Args){
         SecretKey key;
         //sha512(ciao)
         String encami = "This is a test pharse. Thanks!!";
         String password = "a0c299b71a9e59d5ebb07917e70601a3570aa103e99a7bb65a58e780ec9077b1902d1dedb31b1457beda595fe4d71d779b6ca9cad476266cc07590e31d84b206";
         byte[] iv = new byte[16];
         byte[] salt = new byte[32];
         iv = generateIv();
         salt = generateSalt();
         String ll = DatatypeConverter.printHexBinary(iv);
         String lp = DatatypeConverter.printHexBinary(salt);
         System.out.println(ll);
         System.out.println(lp);
         key = getSecretKey(password, salt);
         byte tt[] = new byte[32];
         tt = key.getEncoded();
         String lo = DatatypeConverter.printHexBinary(tt);
         System.out.println(lo);
         String outenc = encrypt(encami, key, iv);
         System.out.println(outenc);
     }
}

在下面的 C 程序中,iv 和 salt 使用上述 Java 程序给出的值进行初始化。由于文本长度为 32 字节,因此无需填充。

#include <stdio.h>
#include <gcrypt.h>
#include <stdlib.h>
#include <string.h>

int
main (void)
{
    int i;
    char *encami = "This is a test pharse. Thanks!!";
    char *pwd = "a0c299b71a9e59d5ebb07917e70601a3570aa103e99a7bb65a58e780ec9077b1902d1dedb31b1457beda595fe4d71d779b6ca9cad476266cc07590e31d84b206";
    unsigned char iv[] = {};
    unsigned char salt[] = {};
    int algo = gcry_cipher_map_name("aes256");
    unsigned char *devkey = NULL;
    unsigned char *enc_buf = NULL;

    enc_buf = gcry_malloc(32);
    devkey = gcry_malloc_secure (32);

    gcry_cipher_hd_t hd;
    gcry_cipher_open(&hd, algo, GCRY_CIPHER_MODE_CBC, 0);
    gcry_kdf_derive (pwd, strlen(pwd)+1, GCRY_KDF_PBKDF2, GCRY_MD_SHA256, salt, 32, 2048, 32, devkey);
    for (i=0; i<32; i++)
        printf ("%02x", devkey[i]);
    printf("\n");
    gcry_cipher_setkey(hd, devkey, 32);
    gcry_cipher_setiv(hd, iv, 16);

    gcry_cipher_encrypt(hd, enc_buf, strlen(encami)+1, encami, strlen(encami)+1);

    for (i=0; i<32; i++)
        printf("%02x", enc_buf[i]);
    printf("\n");
    gcry_cipher_close(hd);
    gcry_free(enc_buf);
    gcry_free (devkey);
    return 0;
}

我的问题是这两个程序中的派生密钥不一样。为什么?
充气城堡的推导功能是不是和 gcry_kdf_derive 不一样?



谢谢!

【问题讨论】:

    标签: java c aes bouncycastle libgcrypt


    【解决方案1】:

    我现在查看了 BC 提供程序中的 PBEWithSHA256And256BitAES-CBC-BC 算法,发现它与 GCRY_KDF_PBKDF2 不兼容。 gcrypt 算法是 PKCS#5 2.0 Scheme 2,而 BC 算法实际上是在实现 PKCS#12。

    实际上,到目前为止,我还没有在提供程序中找到与 gcrypt 匹配的命名算法,但是我能够直接使用 BC API 来获得匹配结果,如下所示。

    充气城堡:

        byte[] salt = new byte[8];
        Arrays.fill(salt, (byte)1);
        PBEParametersGenerator pGen = new PKCS5S2ParametersGenerator(new SHA256Digest());
        pGen.init(Strings.toByteArray("password"), salt, 2048);
        KeyParameter key = (KeyParameter)pGen.generateDerivedParameters(256);
        System.out.println(Hex.toHexString(key.getKey()));
    

    gcrypt:

    unsigned char salt[8];
    memset(salt, 1, 8);
    
    unsigned char key[32];
    gcry_kdf_derive("password", 8, GCRY_KDF_PBKDF2, GCRY_MD_SHA256, salt, 8, 2048, 32, key);
    
    for (int i = 0; i < 32; ++i)
        printf("%02x", key[i]);
    printf("\n");
    

    两者都输出:

    4182537a153b1f0da1ccb57971787a42537e38dbf2b4aa3692baebb106fc02e8

    【讨论】:

      【解决方案2】:

      您的 32 字节计数 (encami) 中似乎包含一个终止 NULL 字符,这解释了不同的输出。 Java 版本看到 31 个字符的输入并提供单个 PKCS#7 填充输出块(PKCS#7 将使用单个“1”字节填充输入)。 C 版本传递 32 个字节,包括最后的“0”字节。所以输入是不同的。

      我建议您停止将 NULL 终止符视为输入的一部分;而是应用 PKCS#7 填充,就像 Java 版本一样。我不熟悉 gcrypt,所以我不知道这样做的典型方法是什么,但是 PKCS#7 填充无论如何都是一个非常简单的概念。

      【讨论】:

      • 您好,感谢您的回答。然而,这只能解释为什么我得到一个不同的加密输出,但它不能解释为什么我得到一个不同的派生密钥(我尝试给出没有 NULL 的密钥,它显然改变了,但它不一样和 Java 一样)。
      • 您还在 KDF 输入中的“gcry_kdf_derive”调用中包含了“pwd”的 NULL,而 Java 版本没有这样做。只是在那儿使用'strlen(pwd)'?
      • 我已经尝试只提供strlen(pwd),但没有任何改变。在这一点上我认为Java使用的密钥派生方法与Gcrypt使用的不同。虽然这很奇怪,因为它是 PBKDF2-PKCS#5 的标准...
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-08
      • 2017-09-08
      • 1970-01-01
      • 1970-01-01
      • 2011-11-08
      相关资源
      最近更新 更多