【问题标题】:AES256 OpenSSL C++ and Java crypto : BadPaddingException while decryptingAES256 OpenSSL C++ 和 Java 加密:解密时出现 BadPaddingException
【发布时间】:2021-09-14 10:52:07
【问题描述】:

我正在开发一个 C++/Qt 客户端与 Java 服务器通信的程序。客户端使用 OpenSSL 库,服务器端使用 Crypto 库。以下是两者中用到的函数:

服务器端:

public static String encryptWithKey(String plaintext, String key) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecureRandom randomSecureRandom = new SecureRandom();
            byte[] iv = new byte[IVSIZE];
            randomSecureRandom.nextBytes(iv);
            IvParameterSpec ivspec = new IvParameterSpec(iv);
            SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(key.getBytes("UTF-8")),"AES");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
            
            byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
            
            ByteArrayOutputStream b = new ByteArrayOutputStream();
            b.write(iv);
            b.write(ciphertext);

            return Base64.getEncoder().encodeToString(b.toByteArray());
            
        } catch (Exception e) {
            System.err.println("Error while encrypting: " + e.toString());
        }
        
        return null;
    }
    
    public static String decryptWithKey(String ciphertext, String key) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            byte[] ciph = Base64.getDecoder().decode(ciphertext.getBytes());
            byte[] iv = Arrays.copyOfRange(ciph , 0, IVSIZE);
            IvParameterSpec ivspec = new IvParameterSpec(ciph,0,IVSIZE);
            SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(key.getBytes("UTF-8")),"AES");
            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivspec);
            byte[] plaintext = cipher.doFinal(ciph);
            return new String(Arrays.copyOfRange(plaintext, IVSIZE, plaintext.length));
        } catch (Exception e) {
            System.err.println("Error while decrypting: " + e.toString());
        }
        return null;
    }

客户端:

int IVLENGTH = 16;
int KEYSIZE = 32;

QByteArray encryptAESWithExistingKey(QByteArray Base64AESKey, QByteArray &data){
    const char *temp_key=QByteArray::fromBase64(Base64AESKey).toStdString().c_str();
    //const char *temp_iv="Y)S$adw`=Tz2AFk";
    const char *temp_iv=randomBytes(IVLENGTH).data();
    unsigned char key[KEYSIZE];
    unsigned char iv[IVLENGTH];

    for(uint i=0;i<strlen(temp_key);i++){
        key[i]=temp_key[i];
    }

    EVP_CIPHER_CTX *en = EVP_CIPHER_CTX_new();
    EVP_CIPHER_CTX_init(en);

    if(!EVP_EncryptInit_ex(en, EVP_aes_256_cbc(),NULL,key, iv))
    {
        qCritical() << "EVP_EncryptInit_ex() failed " << ERR_error_string(ERR_get_error(), NULL);
        return QByteArray();
    }

    for(uint i=0;i<strlen(temp_iv);i++){
        iv[i]=temp_iv[i];
    }

    QByteArray str;
    for(int i=0;i<IVLENGTH;i++) {
        str.append(iv[i]);
    }

    char *input = data.data();
    int len = data.size();

    int c_len = len + AES_BLOCK_SIZE, f_len = 0;
    unsigned char *ciphertext = (unsigned char*)malloc(c_len);

    if(!EVP_EncryptInit_ex(en, NULL, NULL, NULL, NULL))
    {
        qCritical() << "EVP_EncryptInit_ex() failed " << ERR_error_string(ERR_get_error(), NULL);
        return QByteArray();
    }


    if(!EVP_EncryptUpdate(en, ciphertext, &c_len,(unsigned char *)input, len))
    {
        qCritical() << "EVP_EncryptUpdate() failed " << ERR_error_string(ERR_get_error(), NULL);
        return QByteArray();
    }

    if(!EVP_EncryptFinal(en, ciphertext+c_len, &f_len))
    {
        qCritical() << "EVP_EncryptFinal_ex() failed "  << ERR_error_string(ERR_get_error(), NULL);
        return QByteArray();
    }

    len = c_len + f_len;
    EVP_CIPHER_CTX_cipher(en);

    QByteArray encrypted = QByteArray(reinterpret_cast<char*>(ciphertext), len);
    QByteArray finished;
    finished.append(str);
    finished.append(encrypted);

    free(ciphertext);

    return finished.toBase64();
}

QByteArray decryptAESWithExistingKey(QByteArray Base64AESKey, QByteArray &data){
    unsigned char key[KEYSIZE];
    unsigned char iv[IVLENGTH];
    
    data=QByteArray::fromBase64(data);
    QByteArray temp_iv=data.mid(0,IVLENGTH);
    data=data.mid(IVLENGTH);

    const char *temp_key=QByteArray::fromBase64(Base64AESKey).toStdString().c_str();

    for(uint i=0;i<strlen(temp_key);i++){
        key[i]=temp_key[i];
    }

    EVP_CIPHER_CTX *de = EVP_CIPHER_CTX_new();
    EVP_CIPHER_CTX_init(de);

    if(!EVP_DecryptInit_ex(de,EVP_aes_256_cbc(), NULL, key, iv))
    {
        qCritical() << "EVP_DecryptInit_ex() failed" << ERR_error_string(ERR_get_error(), NULL);
        return QByteArray();
    }

    for(int i=0;i<temp_iv.length();i++){
        iv[i]=temp_iv.at(i);
    }

    QByteArray str;
    for(int i=0;i<IVLENGTH;i++) {
        str.append(iv[i]);
    }

    char *input = data.data();
    int len = data.size();

    int p_len = len, f_len = 0;
    unsigned char *plaintext = (unsigned char *)malloc(p_len + AES_BLOCK_SIZE);

    if(!EVP_DecryptUpdate(de, plaintext, &p_len, (unsigned char *)input, len))
    {
        qCritical() << "EVP_DecryptUpdate() failed " <<  ERR_error_string(ERR_get_error(), NULL);
        return QByteArray();
    }

    if(!EVP_DecryptFinal_ex(de,plaintext+p_len,&f_len))
    {
        qCritical() << "EVP_DecryptFinal_ex() failed " <<  ERR_error_string(ERR_get_error(), NULL);
        return QByteArray();
    }

    len = p_len + f_len;

    EVP_CIPHER_CTX_cleanup(de);

    QByteArray decrypted = QByteArray(reinterpret_cast<char*>(plaintext), len);
    free(plaintext);

    return decrypted;
}

我对加密编码很陌生,如果上面的代码看起来很糟糕,那只是我的经验不足。

问题是当我在客户端加密一个字符串并将其发送到服务器进行解密时,我得到:

BadPaddingException:给定的最终块未正确填充。如果在解密期间使用了错误的密钥,则可能会出现此类问题。

我做错了什么?我在服务器端和客户端的主要函数中进行了以下函数调用:

服务器端:

String key="YY3jic+tKY4O5+jLiJ/l2RHYORyIX8TeJ7TUy7Flbhg=";
String str="Hello server!"; //String as received from client
str=decryptWithKey(str,key);

if(str.equals("Hello server!")){
    String sendback=encryptWithKey("Hello client!",key);
    //Send encrypted string back to client
}

客户端:

QByteArray key="YY3jic+tKY4O5+jLiJ/l2RHYORyIX8TeJ7TUy7Flbhg=";
QByteArray message="Hello server!";
QByteArray enc=encryptAESWithExistingKey(key,message);

//Send to server and receive response
QByteArray response;
//Receive ciphertext from server and store it into "response"
qDebug()<<decryptAESWithExistingKey(key,response);
//Expected ouput: "Hello client"

以上代码在服务器中解密失败。由于在服务器上抛出了 BadPaddingException,因此它不会加密或返回任何内容。因此,我的客户端在超时后断开连接。

我以 Base64 格式提供预先生成的 AES256 密钥作为函数参数。我尝试使用由 OpenSSL 和 Java 的加密库生成的不同 AES256 密钥。当使用一个库生成密钥时,它通过与另一端的安全连接共享。显然,对于特定的运行时,双方都使用了相同的密钥。我使用以下代码生成密钥:

JAVA:

public byte[] generateNewAESKey(String password){
        byte[] key = "".getBytes();     
        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            KeySpec spec = new PBEKeySpec(password.toCharArray(), RandomStringUtils.randomAlphanumeric(password.length()).getBytes(), 65536, 256);
            SecretKey tmp = factory.generateSecret(spec);
            SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
            key = secretKey.getEncoded();
        } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
            Logger.getLogger(LoginServiceThread.class.getName()).log(Level.SEVERE, null, ex);
        }
        return key;
    }

注意:RandomStringUtils 来自 Apache Commons lang3 包。

OPENSSL C++:

int SALTSIZE = 8;
int KEYSIZE = 32;

QByteArray generateRandomAES(){
    QByteArray msalt = randomBytes(SALTSIZE);
    QByteArray passphrase = randomBytes(KEYSIZE);
    QByteArray returnkey;

    int rounds = 65536;
    unsigned char key[KEYSIZE];
    unsigned char iv[IVLENGTH] ;

    const unsigned char* salt = (const unsigned char*) msalt.constData();
    const unsigned char* password = (const unsigned char*) passphrase.constData();

    int i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha256(), salt, password, passphrase.length(),rounds,key,iv);


    for(int i=0;i<KEYSIZE;i++) {
        returnkey.append(key[i]);
    }

    return returnkey.toBase64();
}

我做错了什么?为什么两者之间的加密和解密不能顺利进行?请帮我解决这个问题。

【问题讨论】:

  • 我建议toStdString().c_str(); 是一个基本问题该密钥的来源已经是一个base64编码的字符串。解码应生成 AES 密钥 bytes(可能包含零个或多个空八位字节)。因此,一旦 base64 解码完成,字符串操作是合适的。换句话说,如果输入的 base64 编码字符串实际上是解码后的真正 AES 密钥(坦率地说,我看不出它不可能),那么其中代码中的大部分密钥管理都会被破坏。我也不明白为什么会出现看似额外的 EVP_EncryptInit_ex 调用。
  • 嗨 WhozCraig。感谢您的评论。如果没有额外的 EVP_EncryptInit_ex 调用,Java 端会抱怨“java.lang.IllegalArgumentException:IV 缓冲区对于给定的偏移量/长度组合太短”。我尝试删除 toStdString().c_str() ,但似乎没有任何区别。我得到相同的 BadPaddingException。
  • @BharathRam 那些 encryptdecrypt 函数会在任何错误时泄漏内存。在 C++ 中,使用 std::vector&lt;char&gt;,而不是 malloc。是的,如果数据可能包含嵌入的空值,则任何依赖空终止字符串或使用空终止符“停止”的 C++ 函数都无效。请检查所有可能性。
  • @PaulMcKenzie 非常感谢您的评论。我会记住这一点并进行必要的更改。

标签: java c++ encryption openssl aes


【解决方案1】:

我看到的最重要的问题是您错误地使用了iv。您需要将iv 提供给EVP_EncryptInit_ex,但您不需要。您需要在那里提供iv,然后将其发送到另一端。

问题出在这部分:

for(uint i=0;i<strlen(temp_key);i++){
    key[i]=temp_key[i];
}

EVP_CIPHER_CTX *en = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(en);

if(!EVP_EncryptInit_ex(en, EVP_aes_256_cbc(),NULL,key, iv))
{
    qCritical() << "EVP_EncryptInit_ex() failed " << ERR_error_string(ERR_get_error(), NULL);
    return QByteArray();
}

for(uint i=0;i<strlen(temp_iv);i++){ // <<-- here is incorrect. need to be before EVP_EncryptInit_ex
    iv[i]=temp_iv[i];
}

cmets 中还提到了其他问题,但这是主要问题。解密函数也存在同样的问题。

【讨论】:

  • 这成功了!感谢您抽出时间回答这个问题,Afshin。这是一个愚蠢的错误,我没有意识到!我还犯了一些其他错误,例如没有从服务器端的 byte[] 中删除附加的 IV 等等。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-04
相关资源
最近更新 更多