DES/AES ciphertext transmission using Java and C/C++

Keywords: Java OpenSSL C++ less

It was thought that DES and AES, the popular encryption algorithms, should be simple to use. However, two variables were found after the study:

  1. The way of partitioning. Encryption is done block by block. Blocking methods are: CBC, ECB, CFB...
  2. The way of padding. When the number of bits of data is less than the size of the block, it needs to be filled. Filling methods are: NoPadding, PKCS5Padding...

If the encryption and decryption end uses different block or padding methods, even if they all adopt DES/AES algorithm, the decryption can not be successful. Last time we needed C and Java for ciphertext transmission, we knelt on this point (there was no time to study at that time).

Reference article: Java AES algorithm and openssl pairing, mainly through its understanding of openssl in the more advanced EVP series API (its default padding and java are PKCS5 Padding), saving search time.

The code has been pasted, and the following code test has passed. Both Java and C++ can correctly decrypt each other's ciphertext.

Convention: Blocking and padding adopt Java default ECB + PKCS5 Padding.
Encryption program, input is "src.txt" file (encryption does not need plain text, just for readability), output is saved in "enc.bin" file.
Decoder program, input is "enc. bin" file, output is saved in "dec. txt" file.

DES: 
Java Encryption code:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.GeneralSecurityException;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class DesEnc {
    public static byte[] desEncrypt(byte[] source, byte rawKeyData[])
            throws GeneralSecurityException {
        // Processing key
        SecretKeySpec key = new SecretKeySpec(rawKeyData, "DES");
        // encryption
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(source);
    }
    public static void main(String[] args) throws Exception {
        // DES algorithm requires 64 bits (8 bytes)
        byte rawKeyData[] = "hellodes".getBytes("UTF-8");
        // Read the contents of the file (to simply ignore error handling)
        File file = new File("src.txt");
        byte[] source = new byte[(int) file.length()];
        FileInputStream is = new FileInputStream(file);
        is.read(source, 0, (int) file.length());
        is.close();
        // encryption
        byte[] enc = desEncrypt(source, rawKeyData);
        System.out.println("desEncrypt:" + source.length + "->" + enc.length);
        // output to a file
        FileOutputStream os = new FileOutputStream(new File("enc.bin"));
        os.write(enc, 0, enc.length);
        os.close();
    }
}
Java Decrypt code:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.GeneralSecurityException;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class DesDec {
    public static byte[] desDecrypt(byte[] data, byte rawKeyData[])
            throws GeneralSecurityException {
        // Processing key
        SecretKeySpec key = new SecretKeySpec(rawKeyData, "DES");
        // Decrypt
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(data);
    }
    public static void main(String[] args) throws Exception {
        // DES algorithm requires 64 bits (8 bytes)
        byte rawKeyData[] = "hellodes".getBytes("UTF-8");
        // Read the contents of the file (to simply ignore error handling)
        File file = new File("enc.bin");
        byte[] data = new byte[(int) file.length()];
        FileInputStream is = new FileInputStream(file);
        is.read(data, 0, (int) file.length());
        is.close();
        // encryption
        byte[] dec = desDecrypt(data, rawKeyData);
        System.out.println("desDecrypt:" + data.length + "->" + dec.length);
        // output to a file
        FileOutputStream os = new FileOutputStream(new File("dec.txt"));
        os.write(dec, 0, dec.length);
        os.close();
    }
}
C++Encryption code:
#include see later

// Note: All parameters and return values are binary data
std::string desEncrypt(const std::string& source, const std::string& key)
{
    EVP_CIPHER_CTX ctx;
    EVP_CIPHER_CTX_init(&ctx);
    int ret = EVP_EncryptInit_ex(&ctx, EVP_des_ecb(), NULL, (const unsigned char*)key.data(), NULL);
    assert(ret == 1);
    unsigned char* result = new unsigned char[source.length() + 64]; // Make a big enough space
    int len1 = 0;
    ret = EVP_EncryptUpdate(&ctx, result, &len1, (const unsigned char*)source.data(), source.length());
    assert(ret == 1);
    int len2 = 0;
    ret = EVP_EncryptFinal_ex(&ctx, result+len1, &len2); 
    assert(ret == 1);
    std::cout << len1 << "," << len2 << std::endl;
    ret = EVP_CIPHER_CTX_cleanup(&ctx);
    assert(ret == 1);
    std::string res((char*)result, len1+len2);
    delete[] result;
    return res;
}
int main()
{
    std::string key("hellodes", 8);    // Binary data, not strings ending in 0
    // Read the contents of the file (for simplicity, consider the contents of the file & lt; 100K)
    char buf[1024*100];
    FILE* fp = fopen("src.txt", "rb");
    int bytes = fread(buf, 1, 1024*100, fp);
    fclose(fp);
    std::string source(buf, bytes); // binary data
    // encryption
    std::string enc = desEncrypt(source, key);
    std::cout << "desEncrypt:" << source.length() << "->" << enc.length() << std::endl;
    // output to a file
    fp =  fopen("enc.bin", "wb");
    fwrite(enc.data(), 1, enc.length(), fp);
    fclose(fp);
}
C++Decrypt code:
#include see later

// Note: All parameters and return values are binary data
std::string desDecrypt(const std::string& ciphertext, const std::string& key)
{
    EVP_CIPHER_CTX ctx;
    EVP_CIPHER_CTX_init(&ctx);
    int ret = EVP_DecryptInit_ex(&ctx, EVP_des_ecb(), NULL, (const unsigned char*)key.data(), NULL);
    assert(ret == 1);
    unsigned char* result = new unsigned char[ciphertext.length() + 64]; // Make a big enough space
    int len1 = 0;
    ret = EVP_DecryptUpdate(&ctx, result, &len1, (const unsigned char*)ciphertext.data(), ciphertext.length());
    assert(ret == 1);
    int len2 = 0;
    ret = EVP_DecryptFinal_ex(&ctx, result+len1, &len2); 
    assert(ret == 1);
    std::cout << len1 << "," << len2 << std::endl;
    ret = EVP_CIPHER_CTX_cleanup(&ctx);
    assert(ret == 1);
    std::string res((char*)result, len1+len2);
    delete[] result;
    return res;
}
int main()
{
    std::string key("hellodes", 8);    // Binary data, not strings ending in 0
    // Read the contents of the file (for simplicity, consider the contents of the file & lt; 100K)
    char buf[1024*100];
    FILE* fp = fopen("enc.bin", "rb");
    int bytes = fread(buf, 1, 1024*100, fp);
    fclose(fp);
    std::string data(buf, bytes); // binary data
    // encryption
    std::string dec = desDecrypt(data, key);
    std::cout << "desDecrypt:" << data.length() << "->" << dec.length() << std::endl;
    // output to a file
    fp =  fopen("dec.txt", "wb");
    fwrite(dec.data(), 1, dec.length(), fp);
    fclose(fp);
}
------------------------------------------
AES: 
Java Encryption code:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.GeneralSecurityException;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AesEnc {
    public static byte[] aesEncrypt(byte[] source, byte rawKeyData[])
            throws GeneralSecurityException {
        // Processing key
        SecretKeySpec key = new SecretKeySpec(rawKeyData, "AES");
        // encryption
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(source);
    }
    public static void main(String[] args) throws Exception {
        // AES algorithm requires 128 bits (16 bytes)
        byte rawKeyData[] = "helloaeshelloaes".getBytes("UTF-8");
        // Read the contents of the file (to simply ignore error handling)
        File file = new File("src.txt");
        byte[] source = new byte[(int) file.length()];
        FileInputStream is = new FileInputStream(file);
        is.read(source, 0, (int) file.length());
        is.close();
        // encryption
        byte[] enc = aesEncrypt(source, rawKeyData);
        System.out.println("aesEncrypt:" + source.length + "->" + enc.length);
        // output to a file
        FileOutputStream os = new FileOutputStream(new File("enc.bin"));
        os.write(enc, 0, enc.length);
        os.close();
    }
}
Java Decrypt code:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.GeneralSecurityException;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AesDec {
    public static byte[] aesDecrypt(byte[] data, byte rawKeyData[])
            throws GeneralSecurityException {
        // Processing key
        SecretKeySpec key = new SecretKeySpec(rawKeyData, "AES");
        // Decrypt
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(data);
    }
    public static void main(String[] args) throws Exception {
        // AES algorithm requires 128 bits (16 bytes)
        byte rawKeyData[] = "helloaeshelloaes".getBytes("UTF-8");
        // Read the contents of the file (to simply ignore error handling)
        File file = new File("enc.bin");
        byte[] data = new byte[(int) file.length()];
        FileInputStream is = new FileInputStream(file);
        is.read(data, 0, (int) file.length());
        is.close();
        // encryption
        byte[] dec = aesDecrypt(data, rawKeyData);
        System.out.println("desDecrypt:" + data.length + "->" + dec.length);
        // output to a file
        FileOutputStream os = new FileOutputStream(new File("dec.txt"));
        os.write(dec, 0, dec.length);
        os.close();
    }
}
C++Encryption code:
#include see later

// Note: All parameters and return values are binary data
std::string aesEncrypt(const std::string& source, const std::string& key)
{
    EVP_CIPHER_CTX ctx;
    EVP_CIPHER_CTX_init(&ctx);
    int ret = EVP_EncryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, (const unsigned char*)key.data(), NULL);
    assert(ret == 1);
    unsigned char* result = new unsigned char[source.length() + 64]; // Make a big enough space
    int len1 = 0;
    ret = EVP_EncryptUpdate(&ctx, result, &len1, (const unsigned char*)source.data(), source.length());
    assert(ret == 1);
    int len2 = 0;
    ret = EVP_EncryptFinal_ex(&ctx, result+len1, &len2); 
    assert(ret == 1);
    std::cout << len1 << "," << len2 << std::endl;
    ret = EVP_CIPHER_CTX_cleanup(&ctx);
    assert(ret == 1);
    std::string res((char*)result, len1+len2);
    delete[] result;
    return res;
}
int main()
{
    std::string key("helloaeshelloaes", 16);    // Binary data, not strings ending in 0
    // Read the contents of the file (for simplicity, consider the contents of the file & lt; 100K)
    char buf[1024*100];
    FILE* fp = fopen("src.txt", "rb");
    int bytes = fread(buf, 1, 1024*100, fp);
    fclose(fp);
    std::string source(buf, bytes); // binary data
    // encryption
    std::string enc = aesEncrypt(source, key);
    std::cout << "aesEncrypt:" << source.length() << "->" << enc.length() << std::endl;
    // output to a file
    fp =  fopen("enc.bin", "wb");
    fwrite(enc.data(), 1, enc.length(), fp);
    fclose(fp);
}
C++Decrypt code:
#include see later

// Note: All parameters and return values are binary data
std::string aesDecrypt(const std::string& ciphertext, const std::string& key)
{
    EVP_CIPHER_CTX ctx;
    EVP_CIPHER_CTX_init(&ctx);
    int ret = EVP_DecryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, (const unsigned char*)key.data(), NULL);
    assert(ret == 1);
    unsigned char* result = new unsigned char[ciphertext.length() + 64]; // Make a big enough space
    int len1 = 0;
    ret = EVP_DecryptUpdate(&ctx, result, &len1, (const unsigned char*)ciphertext.data(), ciphertext.length());
    assert(ret == 1);
    int len2 = 0;
    ret = EVP_DecryptFinal_ex(&ctx, result+len1, &len2); 
    assert(ret == 1);
    std::cout << len1 << "," << len2 << std::endl;
    ret = EVP_CIPHER_CTX_cleanup(&ctx);
    assert(ret == 1);
    std::string res((char*)result, len1+len2);
    delete[] result;
    return res;
}
int main()
{
    std::string key("helloaeshelloaes", 16);    // Binary data, not strings ending in 0
    // Read the contents of the file (for simplicity, consider the contents of the file & lt; 100K)
    char buf[1024*100];
    FILE* fp = fopen("enc.bin", "rb");
    int bytes = fread(buf, 1, 1024*100, fp);
    fclose(fp);
    std::string data(buf, bytes); // binary data
    // encryption
    std::string dec = aesDecrypt(data, key);
    std::cout << "aesDecrypt:" << data.length() << "->" << dec.length() << std::endl;
    // output to a file
    fp =  fopen("dec.txt", "wb");
    fwrite(dec.data(), 1, dec.length(), fp);
    fclose(fp);
}

====================================================================== the post-publication partition line======================================
1. After publication, it was found that the include class libraries in C++ code were erased by the card because of parentheses. In addition, we need to include the following header files:

[string]    
[iostream]    
[stdio.h]    
[assert.h]    
[openssl/objects.h]    
[openssl/evp.h]    

2. DES and AES encryption algorithms are all for data blocks. Java encryption and decryption function parameters use byte array. C++ uses std::string, because this is the simplest way to use byte arrays in C++ (std::string can store binary data, many people did not expect), but the disadvantage is that the number of copies of memory may be slightly more. If you want to optimize copy efficiency, you can use your own encapsulated Buffer class instead of std::string.

Posted by mrpickleman on Mon, 17 Dec 2018 11:36:04 -0800