0

我正在寻找以下 java 代码的等效 C 代码。我正在尝试编写两个应用程序,一个在 java 中,另一个在 C 中。Java 应用程序使用以下逻辑加密/解密“字符串”,并且在使用以下 java 方法时它正在工作。

public class AEScrypt {

    public static String encryptString(String strToEncrypt, String secret, String salt, byte[] iv) {
        try {

            IvParameterSpec ivspec = new IvParameterSpec(iv);

            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            KeySpec keySpec = new PBEKeySpec(secret.toCharArray(), salt.getBytes(), 65536, 256);
            SecretKey secretKeySpec = keyFactory.generateSecret(keySpec);
            SecretKeySpec secretKey = new SecretKeySpec(secretKeySpec.getEncoded(), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
            int length = 0;
            if (strToEncrypt.length() <= 16) {
                length = 16;
            } else if (strToEncrypt.length() > 16 && strToEncrypt.length() <= 32) {
                length = 16;
            }
            strToEncrypt = fixedLengthString(strToEncrypt, length);
            return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception exception) {
            System.out.println("Error while encrypting value : "+exception.getMessage());
        }
        return null;
    }

    private static String fixedLengthString(String string, int length) {
        return String.format("%" + length + "s", string);
    }

    public static String decryptString(String strToDecrypt, String secret, String salt, byte[] iv) {
        try {
            IvParameterSpec ivspec = new IvParameterSpec(iv);

            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            KeySpec keySpec = new PBEKeySpec(secret.toCharArray(), salt.getBytes(), 65536, 256);
            SecretKey secretKeySpec = keyFactory.generateSecret(keySpec);
            SecretKeySpec secretKey = new SecretKeySpec(secretKeySpec.getEncoded(), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivspec);
            return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt))).trim();
        } catch (Exception e) {
            e.getMessage();
        }
        return null;
    }
}

我从上面的 JAVA 代码中了解到的是:

对于加密:

  1. 我们使用 HMAC-sha256 来生成“key”,它需要“salt”、“password”。
  2. 填充输入数据。
  3. 我们使用 AES-CBC-256 加密填充的输入数据,使用上面生成的“key”和“iv”。
  4. 我们使用 base64 对加密数据进行编码。

解密:

  1. 我们使用 HMAC-sha256 来生成“key”,它需要“salt”、“password”。
  2. 我们使用 base64 解码并获取加密数据。
  3. 我们使用 AES-CBC-256 来解密加密数据,使用上面生成的密钥和 iv。
  4. 修剪解密的数据。

为了在 C 中复制相同的内容,我使用了下面链接中的加密/解密方法; EVP对称加解密

为了生成密钥,我使用“PKCS5_PBKDF2_HMAC”和 EVP_MD 作为“EVP_sha256()”。

int PKCS5_PBKDF2_HMAC(const char *pass, int passlen,
                       const unsigned char *salt, int saltlen, int iter,
                       const EVP_MD *digest,
                       int keylen, unsigned char *out);

对于 base64 编码/解码: base64 编码/解码

我还处理了填充和修剪逻辑。但是我从 java 和 c 代码中得到了不同的加密数据。我在这里错过了什么吗?

如果您在 C 中有任何示例函数,那将非常有帮助。

4

1 回答 1

1

对于加密:

  1. 我们使用 HMAC-sha256 来生成“key”,它需要“salt”、“password”。

不,您正在使用带有HMAC-SHA256的PBKDF2 。这与普通的 HMAC-SHA256 完全不同。但是,假设您为其提供正确的参数,您确定的 OpenSSL 函数与此匹配。这也适用于解密步骤 1。

  1. 填充输入数据。

有点。该填充仅适用于最多 16 个字符的输入数据,所有这些字符都是 ASCII(因为您将其编码为 UTF-8,并且任何非 ASCII 字符都会产生一个以上的字节,从而使编码值成为非法长度) . 大多数较长的值会失败,尽管少数会因运气不好而成功。甚至对于“成功”的价值观,也会有一些改变;这被认为是不好的做法,基本上自 1980 年以来所有设计良好的加密方案都旨在保护所有数据。特别是非常常见的 PKCS5(由于技术原因,有时称为 PKCS7 或 PKCS5/PKCS7)标准填充正确地保留了所有数据,并且已经在 J​​ava 和 OpenSSL 以及几乎所有其他体面的加密库和设备中实现,并且将是更好的选择,也更简单。

固定填充后,Java 端可以处理非 ASCII 数据,但前提是您既要对要加密的明文进行编码,又要在解密后对明文进行适当的解码。您有.getBytes(StandardCharsets.UTF_8)on encrypt,但需要将其与new String(cipher.doFinal(...), StandardCharsets.UTF_8)on decrypt 匹配,否则它可能会或可能不会工作,具体取决于您用于运行它的平台和环境。

C面可能更难。OpenSSL 基于在 1995 年和 1999 年版本的 C 开始处理非英文字符之前开始的老式 C 代码,它只理解字节,它可以是单字节又名“窄”字符。您必须使用调用代码来包装它,该代码以多字节编码(例如 UTF-8)处理“宽”字符(并使用字节调用 OpenSSL 部分),或者您必须在程序外部通过控制环境(例如作为终端或模拟器)或文件。您的问题甚至没有提供任何提示,因此不可能提出任何建议。

因为您将“秘密”(密码)、盐和 IV 视为Strings,所以同样的考虑适用于它们,除了它们可能来自与数据不同的来源。IV 和 salt 被设计为字节序列,将 IV 特别限制为 ASCII 甚至 UTF-8 编码可能会降低一些安全性,但由于 SO 的主题是编程而不是安全性,我不会追求这一点。在 PKCS5 中的实际 PBKDF2 密码也是八位字节(Java 字节),但它“建议”将文本(字符)编码为 ASCII 或 UTF-8,而 Java 确实接受char[]PBEKeySpec编码为 UTF-8,因此对于非 ASCII OpenSSL 调用者或环境需要与之匹配。

鉴于这些限制:所有值都只是 ASCII,数据不超过 16 个字符=字节,IV 正好是 16,以下 C 代码匹配并且可以与您的 Java 互操作。错误处理很少,我在一个函数中同时进行加密和解密;您希望能够将它们分开。(更正)

/* SO65195128.c 20dec09,11 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/ssl.h>

void err (int n, const char * s){ printf("%s:%d\n", s, n); ERR_print_errors_fp(stdout); exit(1); }

int main (int argc, char **argv){
    if( argc != 5 || strlen(argv[3]) != 16 || strlen(argv[4]) > 16 ){ printf("bad args\n"); exit(1); }
    const char * pw = argv[1], * salt = argv[2], * iv = argv[3], * org = argv[4];
    unsigned char key [32], pad [16], enc [16], b64 [25], unb [16], dec [16];
    int rc, len, temp, i, j;
    SSL_library_init();

    /* for both */
    rc = PKCS5_PBKDF2_HMAC (pw, strlen(pw), (unsigned char*)salt, strlen(salt), 65536, EVP_sha256(), 32, key);
    if( rc != 1 ) err(rc,"PBKDF2");
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();

    /* for encrypt */
    len = strlen(org); memset(pad, ' ', 16-len); memcpy (pad+16-len, org, len);
    rc = EVP_EncryptInit (ctx, EVP_aes_256_cbc(), key, (unsigned char*)iv);
    if( rc != 1 ) err(rc,"EncryptInit");
    rc = EVP_CIPHER_CTX_set_padding(ctx,0);
    if( rc != 1 ) err(rc,"set_padding");
    rc = EVP_EncryptUpdate (ctx, enc, &len, pad, 16);
    if( rc != 1 || len != 16 ) err(rc,"EncryptUpdate");
    rc = EVP_EncryptFinal (ctx, enc+len, &temp); 
    if( rc != 1 || temp != 0 ) err(rc,"EncryptFinal");
    rc = EVP_EncodeBlock(b64, enc, 16);
    if( rc <= 0 ) err(rc,"EncodeBlock");

    printf ("%.*s\n", rc, b64);

    /* for decrypt */
    rc = EVP_DecodeBlock(unb, b64, /*len*/rc)-(b64[rc-1]=='=')-(b64[rc-2]=='=');
    /* this is a hack, should go for DecodeInit,Update,Final */
    if( rc != 16 ) err(rc,"DecodeBlock");
    rc = EVP_DecryptInit (ctx, EVP_aes_256_cbc(), key, (unsigned char*)iv);
    if( rc != 1 ) err(rc,"DecryptInit");
    rc = EVP_CIPHER_CTX_set_padding(ctx,0);
    if( rc != 1 ) err(rc,"set_padding");
    rc = EVP_DecryptUpdate (ctx, dec, &len, unb, 16);
    if( rc != 1 || len != 16 ) err(rc,"DecryptUpdate");
    rc = EVP_DecryptFinal (ctx, dec+len, &temp); 
    if( rc != 1 || temp != 0 ) err(rc,"DecryptFinal");
    i=0; while(i<16&&dec[i]<=' ') i++; j=16; while(j>0&&dec[j-1]<=' ') j--;

    printf ("%.*s\n", j-i, dec+i);
    /* note this is NOT a C string -- needs to be copied and NUL added for that */

    return 0;
}

(添加)密码/秘密 SEKRIT salt NOSAILOR(一个笑话)和 IV STARTINGSTARTING
数据 SOMEDATA 我得到MOOn6FicaVcnVLokSANQsw==
的数据但我得到5NsJUO4z1Bbap0U85ZClMg==
的两者都与我从你的 Java 得到的相匹配,并正确解密。看看你得到了什么。

于 2020-12-08T23:18:43.043 回答