我正在开发一个 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();
}
我做错了什么?为什么两者之间的加密和解密不能顺利进行?请帮我解决这个问题。
答案 0 :(得分:0)
我看到的最重要的问题是您错误地使用了 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];
}
评论中还提到了其他问题,但这是主要问题。同样的问题也出现在解密函数中。