AES three-terminal encryption and decryption – synchronous implementation of iOS, Android, JS

AES is one of the commonly used encryption algorithms in development. However, because the languages ​​used for front-end and back-end development are not unified, it often occurs that the front-end is encrypted but the back-end cannot be decrypted. However, regardless of the language system, the AES algorithm is always the same, so the reason for the inconsistent results is that the parameters of the encryption settings are inconsistent. So let's take a look at several parameters that need to be unified when using AES encryption on the three platforms.

  • 密钥长度(Key Size)
    
  • 加密模式(Cipher Mode)
    
  • 填充方式(Padding)
    
  • 初始向量(Initialization Vector)
    

key length

Under the AES algorithm, there are three key lengths: 128, 192 and 256 bits. Due to historical reasons, the JDK only supports keys no larger than 128 bits by default, and keys of 128 bits can already meet commercial security requirements. So this example uses AES-128 first. (Java uses a key method larger than 128 bits mentioned at the end)

encryption mode

AES belongs to Block Cipher, and there are several working modes such as CBC, ECB, CTR, OFB, and CFB in block encryption. This example uses the CBC mode uniformly.

filling method

Since block encryption can only encrypt data blocks of a specific length, CBC and ECB modes require data padding before the last data block is encrypted. (CFB, OFB and CTR modes do not need to pad the last plaintext because the encryption operation with the key is the previous encrypted ciphertext)

PKCS7Padding is provided in iOS SDK, PKCS5Padding is provided by JDK, and CryptoJS.pad.Pkcs7 is provided by JS. In principle, PKCS5Padding limits the filled Block Size to 8 bytes. In fact, when the block is larger than this value in Java, its PKCS5Padding is equal to PKCS7Padding and Pkcs7: for every χ bytes that need to be filled, the value of the padded value is χ.

initial vector

To use other encryption modes except ECB, you need to pass in an initial vector whose size is equal to the Block Size (the Block Size of AES is 128 bits), and the three-terminal API indicates that when the initial vector is not passed in, the system will use the default An initial vector of all 0s.

Three-terminal code implementation

Note that the three-terminal implementation is AES-128, so the key passed in by the method needs to be a string of length 16.

  • Java implementation

First define the value of an initial vector.

private static final String IV_STRING = "16-Bytes--String";

encryption:

public static String encryptAES(String content, String key) 
			throws InvalidKeyException, NoSuchAlgorithmException, 
			NoSuchPaddingException, UnsupportedEncodingException, 
			InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {

		byte[] byteContent = content.getBytes("UTF-8");

	    // 注意,为了能与 iOS 统一
	    // 这里的 key 不可以使用 KeyGenerator、SecureRandom、SecretKey 生成
		byte[] enCodeFormat = key.getBytes();
	    SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");
			
	    byte[] initParam = IV_STRING.getBytes();
	    IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);
			
	    // 指定加密的算法、工作模式和填充方式
	    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
	    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
		
	    byte[] encryptedBytes = cipher.doFinal(byteContent);
		
	    // 同样对加密后数据进行 base64 编码
	    Encoder encoder = Base64.getEncoder();
	    return encoder.encodeToString(encryptedBytes);
	}

Decrypt:

public static String decryptAES(String content, String key) 
			throws InvalidKeyException, NoSuchAlgorithmException, 
			NoSuchPaddingException, InvalidAlgorithmParameterException, 
			IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
			
	    // base64 解码
	    Decoder decoder = Base64.getDecoder();
	    byte[] encryptedBytes = decoder.decode(content);
		
	    byte[] enCodeFormat = key.getBytes();
	    SecretKeySpec secretKey = new SecretKeySpec(enCodeFormat, "AES");
		
	    byte[] initParam = IV_STRING.getBytes();
	    IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);

	    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
	    cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);

	    byte[] result = cipher.doFinal(encryptedBytes);
		
	    return new String(result, "UTF-8");
	}

-iOS implementation

First define the value of an initial vector

NSString *const kInitVector = @"16-Bytes--String"

Determine the key length, here select AES-128.

size_t const kKeySize = kCCKeySizeAES128

encryption:

+ (NSString *)encryptAES:(NSString *)content key:(NSString *)key {
    
    NSData *contentData = [content dataUsingEncoding:NSUTF8StringEncoding];
    NSUInteger dataLength = contentData.length;
    
    // 为结束符'\0' +1
    char keyPtr[kKeySize + 1];
    memset(keyPtr, 0, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
    
    // 密文长度 <= 明文长度 + BlockSize
    size_t encryptSize = dataLength + kCCBlockSizeAES128;
    void *encryptedBytes = malloc(encryptSize);
    size_t actualOutSize = 0;
    
    NSData *initVector = [kInitVector dataUsingEncoding:NSUTF8StringEncoding];
    
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmAES,
                                          kCCOptionPKCS7Padding,  // 系统默认使用 CBC,然后指明使用 PKCS7Padding
                                          keyPtr,
                                          kKeySize,
                                          initVector.bytes,
                                          contentData.bytes,
                                          dataLength,
                                          encryptedBytes,
                                          encryptSize,
                                          &actualOutSize);
    
    if (cryptStatus == kCCSuccess) {
        // 对加密后的数据进行 base64 编码
        return [[NSData dataWithBytesNoCopy:encryptedBytes length:actualOutSize] base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    }
    free(encryptedBytes);
    return nil;
}

Decrypt:

+ (NSString *)decryptAES:(NSString *)content key:(NSString *)key {
    // 把 base64 String 转换成 Data
    NSData *contentData = [[NSData alloc] initWithBase64EncodedString:content options:NSDataBase64DecodingIgnoreUnknownCharacters];
    NSUInteger dataLength = contentData.length;
    
    char keyPtr[kKeySize + 1];
    memset(keyPtr, 0, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
    
    size_t decryptSize = dataLength + kCCBlockSizeAES128;
    void *decryptedBytes = malloc(decryptSize);
    size_t actualOutSize = 0;
    
    NSData *initVector = [kInitVector dataUsingEncoding:NSUTF8StringEncoding];
    
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmAES,
                                          kCCOptionPKCS7Padding,
                                          keyPtr,
                                          kKeySize,
                                          initVector.bytes,
                                          contentData.bytes,
                                          dataLength,
                                          decryptedBytes,
                                          decryptSize,
                                          &actualOutSize);
    
    if (cryptStatus == kCCSuccess) {
        return [[NSString alloc] initWithData:[NSData dataWithBytesNoCopy:decryptedBytes length:actualOutSize] encoding:NSUTF8StringEncoding];
    }
    free(decryptedBytes);
    return nil;
}
  • JS implementation

First define the value of an initial vector

var ivString = "16-Bytes--String";

encryption:

Enter image description

Decrypt:

Enter image description

About Java using keys larger than 128 bits

Go to the Oracle official website to download the JCE corresponding to the Java version, decompress it and put it in JAVA_HOME/jre/lib/security/, and then modify the kKeySize on the iOS side and the corresponding key on the three terminals.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325979106&siteId=291194637