i want to create AES encrypt and decrypt GCM mode with authentication tag.
This is my source code of iOS version:
CCCryptorStatus ccStatus = kCCSuccess;
NSData *iv = [hexIV dataUsingEncoding:NSUTF8StringEncoding];
NSData *symmetricKey = [hexIV dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData *dataOut = [NSMutableData dataWithLength:self.length];
NSMutableData *tag = [NSMutableData dataWithLength:kCCBlockSizeAES128];
size_t tagLength = kCCBlockSizeAES128;
ccStatus = CCCryptorGCM(kCCEncrypt,
kCCAlgorithmAES,
symmetricKey.bytes,
kCCKeySizeAES256,
iv.bytes,
iv.length,
aad.bytes,
aad.length,
self.bytes,
self.length,
dataOut.mutableBytes,
tag.bytes,
&tagLength);
if (ccStatus == kCCSuccess) {
return [NSDictionary dictionaryWithObjectsAndKeys:dataOut,#"cyphertext",tag,#"tag",iv,#"iv",nil];
} else {
return nil;
}
I want to change this code to Android version, below Android's source code:
byte[] aKey = hexStringToByteArray(hexKey);
byte[] aIV = hexStringToByteArray(hexIV);
Key key = new SecretKeySpec(aKey, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(16 * Byte.SIZE, aIV));
cipher.updateAAD(aad);
byte[] encrypted = cipher.doFinal(aKey);
How to add authentication tag into my source code?
When i encrypt AES GCM in iOS
let iv = AES.GCM.Nonce()
let sealedBox = try! AES.GCM.seal(rawJsonData,
using: yourKey,
nonce: yourIv)
let cipher = sealedBox.ciphertext + sealedBox.tag
Remember, in android you have: IV + Cipher + Tag
you can easily reproduce this in iOS to decrypt, following the same structure,
let incomingCipher = (payload?.fromBase64())!
let IV = initialVector?.fromBase64()
let combine = IV! + incomingCipher
let myNewSealedBox = try AES.GCM.SealedBox(combined: combine)
Where incomingCipher (Android) Contains cipher + Tag
Related
Code in angular using crypto-js:
let key = '12345123451234512345123451234509';// actual keys are different and has same length of 32 char
let iv = '12345123451234512345123451234509';
let secret_key = CryptoJS.enc.Hex.parse(key);
let secret_iv = CryptoJS.enc.Hex.parse(iv);
let encryptedString = CryptoJS.AES.encrypt(
JSON.stringify(data),
secret_key,
{
iv: secret_iv,
padding: CryptoJS.pad.ZeroPadding
}
).toString();
let requestObj = {
input: encryptedString.trim()
}
I am not able to do same encryption in android.
Android Code
String key32Char = "12345123451234512345123451234509";
String iv32Char = "12345123451234512345123451234509";
byte[] srcBuff = jsonString.getBytes("UTF-8");
//SecretKeySpec secretKeySpec = new SecretKeySpec(key32Char.getBytes(), "AES");
//IvParameterSpec ivParameterSpec = new IvParameterSpec(iv32Char.getBytes());
SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.decode(key32Char, Base64.NO_WRAP), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.decode(iv32Char, Base64.NO_WRAP));
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] dstBuff = cipher.doFinal(srcBuff);
String encryptedString = Base64.encodeToString(dstBuff, Base64.NO_WRAP);
JSONObject requestObj = new JSONObject();
requestObj.put("input", encryptedString);
What CryptoJS.enc.Hex.parse(key) line does ?
How to do same encryption ?
IV and Key:To match the key and IV part both must use either base64 or hex decodings.
In Hex encoded string there are 32 hex char that makes 128-bit. However, the same string can be rejected by base64 decode and if not rejected the output will not be 128-bit. You need to use
byte[] bytes = new BigInteger("7F" + str, 16).toByteArray();
SecretKeySpec key = new SecretKeySpec(bytes, 1, bytes.length-1, "AES");
to convert the hex string into byte array.
padding: CryptoJS.pad.ZeroPadding is useful if your data size is an exact multiple of 128. Otherwise, you need to use this parameter to say that I'll use this for testing my new padding scheme. You need to better use Pkcs7 that was the default.
In Java you need getInstance("AES/CBC/PKCS5Padding");
Mode of operation: The default in JS is CBC, therefor you need the same in Java, as above getInstance("AES/CBC/PKCS5Padding");
Output: To compare the outputs you need to see the same result. In Java you convert the output into base64, so you need the same for JS.
As you can see, you must do the same steps, parameters for both.
Note that: CBC mode is archaic and you should prefer authenticated encryption modes like AES-GCM or ChaCha20-Poly1305. They not only provides confidentiality but also integrity and authentication. Unfortunately, crypto-js doesn't have them. But you can use some other JS libraries for that.
As name suggests, CryptoJS.enc.Hex.parse(key) parses a Hex String and uses it as key. So you need to do the same for your java code.
In addition, you need to select correct encryption mode and padding too. Your CryptoJs code uses CBC mode so you need to do same in Java Code. Your are using zero padding in CryptoJs side which is not available in java, so you need to do it manually. But in general, using zero padding is a bad idea and it is better to use PKCS5 padding for example which is default for CryptoJs.
With these things, these 2 codes match:
let key = '12345123451234512345123451234509';// actual keys are different and has same length of 32 char
let iv = '12345123451234512345123451234509';
let secret_key = CryptoJS.enc.Hex.parse(key);
let secret_iv = CryptoJS.enc.Hex.parse(iv);
let encryptedString = CryptoJS.AES.encrypt(
"0123456789012345x",
secret_key,
{
iv: secret_iv,
}
).toString();
let requestObj = {
input: encryptedString.trim()
}
Java:
public void doit()
{
byte[] key32Char = hexStringToByteArray("12345123451234512345123451234509");
byte[] iv32Char = hexStringToByteArray("12345123451234512345123451234509");
byte[] srcBuff = "0123456789012345x".getBytes();
SecretKeySpec secretKeySpec = new SecretKeySpec(key32Char, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv32Char);
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] dstBuff = cipher.doFinal(srcBuff);
String encryptedString = new String(Base64.getEncoder().encode(dstBuff));
System.out.print(encryptedString);
}
catch(Exception e) {
System.out.print(e.toString());
return;
}
}
public byte[] hexStringToByteArray(String s)
{
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
Update:
If you are forced to use bad idea of zero padding, you need to keep real size of data and do padding manually:
public void doitZeroPadding()
{
...
// For the simplicity, I assume that data size is smaller than 128.
// You need to change this part as needed.
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int dsize = srcBuff.length + 1; // 1 is for plain buffer size
// This line align size to the multiple of block size.
int newBufSize = ((dsize + cipher.getBlockSize() - 1) / cipher.getBlockSize()) * cipher.getBlockSize();
byte[] newSrcBuf = new byte[newBufSize];
// You need real buffer size, or you don't know how long is decrypted buffer.
// I add it inside encrypting buffer to prevent other to see real decrypted buffer size.
// But if you want to have exact same encrypted buffer on both sides, you must remove it.
newSrcBuf[0] = (byte)(srcBuff.length);
System.arraycopy(srcBuff, 0, newSrcBuf, 1, srcBuff.length);
// Now use newSrcBuf/newBufSize
...
}
on the decryption side, check real size from decrypted buffer and use that size starting byte 1 for creating string.
I tried to do AES encryption in Swift which I do in Android like this:
public static String Encrypt(String text, String key) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] keyBytes = new byte[16];
byte[] b = key.getBytes("UTF-8");
int len = b.length;
if (len > keyBytes.length)
len = keyBytes.length;
System.arraycopy(b, 0, keyBytes, 0, len);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(keyBytes);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] results = cipher.doFinal(text.getBytes("UTF-8"));
return Base64.encodeToString(results, Base64.DEFAULT);
}
catch (Exception ex){return "error"+ex.getMessage();}
}
Below is the equivalent code in Swift 3.2:
func aesEncrypt(key: String, iv: String) throws -> String{
let data = self.data(using: String.Encoding.utf8)
let enc = try AES(key: key.bytes, blockMode: BlockMode.CBC(iv: iv.bytes, padding: Padding.pkcs5).encrypt(data!.bytes)
let encData = NSData(bytes: enc, length: Int(enc.count))
let base64String: String = encData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0));
let result = String(base64String)
return result!}
In Android it doesn't matter for key: I can fill with any string (no length limitations). But when using Swift I have to use a 32 charachter string for key and a 16 charachter string for IV, otherwise it will throw an error.
Here is the Swift usage:
let data = "this is string which I want to be encrypted"
let key = "bbbb98232-a343-4343f-2111"
let iv = "0000000000000000" // lenght = 16 like android code
let encryptedString = data.aesEncrypt(key: key, iv: iv);
Is there maybe some mistake in my Swift code?
You can try the below Swift code for AES encryption. Its String extension.
import Foundation
import CommonCrypto
extension String {
func aesEncrypt(key: String, initializationVector: String, options: Int = kCCOptionPKCS7Padding) -> String? {
if let keyData = key.data(using: String.Encoding.utf8),
let data = self.data(using: String.Encoding.utf8),
let cryptData = NSMutableData(length: Int((data.count)) + kCCBlockSizeAES128) {
let keyLength = size_t(kCCKeySizeAES128)
let operation: CCOperation = UInt32(kCCEncrypt)
let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
let options: CCOptions = UInt32(options)
var numBytesEncrypted: size_t = 0
let cryptStatus = CCCrypt(operation,
algoritm,
options,
(keyData as NSData).bytes, keyLength,
initializationVector,
(data as NSData).bytes, data.count,
cryptData.mutableBytes, cryptData.length,
&numBytesEncrypted)
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.length = Int(numBytesEncrypted)
let base64cryptString = cryptData.base64EncodedString(options: .lineLength64Characters)
return base64cryptString
} else {
return nil
}
}
return nil
}
}
I receive from a Server in Python a file encrypted in this manner:
import os
from Crypto.Cipher import AES
from Crypto import Random
def pad(s):
return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def encrypt(message, key):
message = pad(message)
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def encrypt_file(file_name, key):
with open("epub/" + file_name, 'rb') as fo:
plaintext = fo.read()
enc = encrypt(plaintext, key)
file_name = "enc/" + file_name
with open(file_name, 'wb') as fo:
fo.write(enc)
for list in os.listdir('./epub'):
if list.find('.epub') != -1:
key = '0123456789012345'
encrypt_file(list, key)
My App in Android decrypt file in this manner:
FileInputStream epubCifr = new FileInputStream(Environment.getExternalStorageDirectory() + "/Skin/readium/epub_content/" + title + "/" + title + ".epub");
FileOutputStream epubDec = new FileOutputStream(Environment.getExternalStorageDirectory() + "/Skin/readium/epub_content/" + title + "/" + title + "_dec.epub");
SecretKeySpec sks = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, sks);
CipherInputStream cis = new CipherInputStream(epubCifr, cipher);
int b;
byte[] d = new byte[16];
while ((b = cis.read(d)) != -1)
epubDec.write(d, 0, b);
epubDec.flush();
epubDec.close();
cis.close();
Now.
I would do the same thing in an app for iOS but I'm a newbie in objective C.
I can't change code on Server.
I tried to use RNCrypto for iOS but with poor results.
Any suggestions?
Thanks
EDIT 1
Sorry. This is my code in objective C.
I think that there are some settings in RNCryptor that are wrong.
int blockSize = 16;
NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:#"epub.epub"];
NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:#"epub_dec.epub" append:NO];
[cryptedStream open];
[decryptedStream open];
__block NSMutableData *data = [NSMutableData dataWithLength:blockSize];
__block RNEncryptor *decryptor = nil;
dispatch_block_t readStreamBlock = ^{
[data setLength:blockSize];
NSInteger bytesRead = [cryptedStream read:[data mutableBytes] maxLength:blockSize];
if (bytesRead < 0) {
}
else if (bytesRead == 0) {
[decryptor finish];
}
else {
[data setLength:bytesRead];
[decryptor addData:data];
NSLog(#"Sent %ld bytes to decryptor", (unsigned long)bytesRead);
}
};
decryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings
password:#"0123456789012345"
handler:^(RNCryptor *cryptor, NSData *data) {
NSLog(#"Decryptor recevied %ld bytes", (unsigned long)data.length);
[decryptedStream write:data.bytes maxLength:data.length];
if (cryptor.isFinished) {
[decryptedStream close];
}
else {
readStreamBlock();
}
}];
readStreamBlock();
EDIT 2
Python code with PKCS7 and iv of 0 bytes.
from Crypto.Cipher import AES
from Crypto import Random
from pkcs7 import PKCS7Encoder
def pad(s):
encoder = PKCS7Encoder()
return encoder.encode(s)
#return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def encrypt(message, key):
message = pad(message)
#iv = Random.new().read(AES.block_size)
iv = b'0000000000000000'
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def encrypt_file(file_name, key):
with open("epub/" + file_name, 'rb') as fo:
plaintext = fo.read()
enc = encrypt(plaintext, key)
file_name = "enc/" + file_name
with open(file_name, 'wb') as fo:
fo.write(enc)
key = '0123456789012345'
encrypt_file("epub1.epub", key)
decrypt_file("epub1.epub", key)
RNCryptor in this case is not a good choice because it imposes a format and method for encryption that does not match the method being used to encrypt.
You state that there is no padding but the Python code does pad the message with zero bytes, this is both non-standard, insecure and assumes that the data being encrypted does not end with a null byte. You will have to handle stripping the null padding after decryption. The usual padding is PKCS#7.
The iv for CBC mode is prepended to the encrypted data, this is a good scheme. You will need to separate the iv from the encryption data and apply it to the decryption function call.
It is not clear that the encryption key is exactly the correct length or what AES key size is being used. Presumably the Python method is using the length of the key to determine the AES key size, it can be 128, 192 or 256 bits and if not one of these padding the key to length. You will need to determine this and handle it in your code. The AES key size should really be specified and the key should be exactly the matching length.
With the above you can use the Apple supplied Common Crypto, specifically CCCrypt function.
It is potentially possible to modify the received data to match the requirements of RNCryptor and handle the padding after the decryption.
I need to encrypt password, but for Android 4.2 and below version, my solution doesn't work, The encrypted password is random. it's due to PRNG. So I saw this post :
https://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html
I implemented PRNGFixes, but it does not change...
How to solve this problem of randomly generated number ?
Code :
KeySpec spec = new PBEKeySpec(PASSWORD.toCharArray(), byteSalt, NB_ITER_RFC, SIZE_KEY);
SecretKey temp = factory.generateSecret(spec);
Cipher c = Cipher.getInstance(DES_EDE_PKCS5);
IvParameterSpec ivParam = new IvParameterSpec(bytesIv);
c.init(Cipher.ENCRYPT_MODE, temp, ivParam);
byte[] encrypted = c.doFinal(texteAChiffrer.getBytes("UTF-8"));
mdp = Base64.encodeToString(encrypted, Base64.DEFAULT);
OR :
PBEKeySpec pbeKeySpec = new PBEKeySpec(PASSWORD.toCharArray(), byteSalt, NB_ITER_RFC, SIZE_KEY);
byte[] key2 = PBEParametersGenerator.PKCS12PasswordToBytes(pbeKeySpec.getPassword());
SecretKey temp2 = factory.generateSecret(pbeKeySpec);
Cipher c2 = Cipher.getInstance(DES_EDE_PKCS5);
c2.init(Cipher.ENCRYPT_MODE, temp2, ivParam);
byte[] encrypted2 = c2.doFinal(texteAChiffrer.getBytes("UTF-8"));
mdp = Base64.encodeToString(encrypted2, Base64.DEFAULT);
This two solutions give the same results from Android 4.3 and latest versions (4.4 and 5.0)
Thank you for your help :)
I'm trying do some encrypt something using 3des on the iOS that must match the results from java and .NET.
Java code is :
public class EncryptionHelper {
// Encrypts string and encode in Base64
public static String encryptText(String plainText,String key, String IV) throws Exception {
// ---- Use specified 3DES key and IV from other source --------------
byte[] plaintext = plainText.getBytes();//input
byte[] tdesKeyData = key.getBytes();// your encryption key
byte[] myIV = IV.getBytes();// initialization vector
Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec(tdesKeyData, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(myIV);
c3des.init(Cipher.ENCRYPT_MODE, myKey, ivspec);
byte[] cipherText = c3des.doFinal(plaintext);
String encryptedString = Base64.encodeToString(cipherText,
Base64.DEFAULT);
// return Base64Coder.encodeString(new String(cipherText));
return encryptedString;
}
}
and iOS code for the same is :
-(NSString*)new3DESwithoperand:(NSString*)plaintext encryptOrDecrypt:(CCOperation)encryptorDecrypt key:(NSString*)key initVec:(NSString*)initVec
{
NSData* data = [plaintext dataUsingEncoding:NSUTF8StringEncoding];
const void *vplainText = [data bytes];;
size_t plainTextBufferSize = [data length];
NSLog(#"%#, Length: %u",[data description],[data length]);
size_t bufferPtrSize = (plainTextBufferSize + kCCBlockSize3DES) & ~(kCCBlockSize3DES - 1);
NSLog(#"%zu, sizof of uint8_t: %zu",bufferPtrSize, sizeof(uint8_t));
size_t movedBytes = 0;
uint8_t *bufferPtr = malloc( bufferPtrSize * sizeof(uint8_t));
NSLog(#"%zu",sizeof(bufferPtr));
memset((void*)bufferPtr, 0x0, bufferPtrSize);
NSLog(#"%zu",sizeof(bufferPtr));
const void * vkey = [[NSData base64DataFromString:key] bytes];
const void *vinitVec = [[NSData base64DataFromString:initVec] bytes];
NSLog(#"vinitvec: %#",[[NSData base64DataFromString:initVec] description]);
CCCryptorStatus ccStatus;
ccStatus = CCCrypt(encryptorDecrypt,
kCCAlgorithm3DES,
kCCOptionPKCS7Padding & kCCModeCBC,
vkey,
kCCKeySize3DES,
vinitVec,
vplainText,
plainTextBufferSize,
(void*)bufferPtr,
bufferPtrSize,
&movedBytes);
NSData* result = [NSData dataWithBytes:(const void*)bufferPtr length:(NSUInteger)movedBytes];
NSString* str = [NSString base64StringFromData:result length:result.length];
NSLog(#"%#",str);
return str;
}
This code successfully encrypts and decrypts a string. However, it does not match the results from .NET and java.
Thank you
Have found a solution for the above problem of encryption value generated different on iOS and .NET or Java.
Solution:
1. In Android and .NET you have to use a key of size 16 Bytes (eg: key="1234567890123456")
In iOS you need to use a key size of 24 bytes but the generation of key makes a little difference.
Use the same key as used in Android or .NET (16 bytes) and append it with the first 8 Bytes of the same key.
key16Byte = "1234567890123456" //Android and .NET key
key24Byte = key16Byte + "12345678" //ios and Java key, Replicated first 8 Bytes of 16Byte key
//new24ByteKey = "123456789012345612345678"
Remove "& kCCModeCBC" from CCypher Mode.
Some values require bytes in CCCrypt function which I have changed in the below mentioned code below. like keyData, encryptData.
Reason for different encryption generated:
Android and .NET - It takes 16Byte key and internally replicates, and generates a 24Byte key.
Java - It throws an Exception "Invalid key length", if you provide a 16Byte key value.
iOS - It generates encryption value with 16Byte and 24Byte both values without throwing any exception, so is the reason we get a different encryption generated in case of 16Byte key.
Java Code
public class EncryptionHelper {
// Encrypts string and encode in Base64
public static String encryptText(String plainText,String key, String IV) throws Exception {
// ---- Use specified 3DES key and IV from other source --------------
byte[] plaintext = plainText.getBytes();//input
byte[] tdesKeyData = key.getBytes();// your encryption key
byte[] myIV = IV.getBytes();// initialization vector
Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec(tdesKeyData, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(myIV);
c3des.init(Cipher.ENCRYPT_MODE, myKey, ivspec);
byte[] cipherText = c3des.doFinal(plaintext);
String encryptedString = Base64.encodeToString(cipherText,
Base64.DEFAULT);
// return Base64Coder.encodeString(new String(cipherText));
return encryptedString;
}
iOS Code:
- (NSString *)encrypt:(NSString *)encryptValue key:(NSString *)key24Byte IV:(NSString *)IV{
// first of all we need to prepare key
if([key length] != 24)
return #"Require 24 byte key, call function generate24ByteKeySameAsAndroidDotNet with a 16Byte key same as used in Android and .NET"; //temporary error message
NSData *keyData = [key24Byte dataUsingEncoding:NSUTF8StringEncoding];
// our key is ready, let's prepare other buffers and moved bytes length
NSData *encryptData = [encryptValue dataUsingEncoding:NSUTF8StringEncoding];
size_t resultBufferSize = [encryptData length] + kCCBlockSize3DES;
unsigned char resultBuffer[resultBufferSize];
size_t moved = 0;
// DES-CBC requires an explicit Initialization Vector (IV)
// IV - second half of md5 key
NSMutableData *ivData = [[IV dataUsingEncoding:NSUTF8StringEncoding]mutableCopy];
NSMutableData *iv = [NSMutableData dataWithData:ivData];
CCCryptorStatus cryptorStatus = CCCrypt(kCCEncrypt, kCCAlgorithm3DES,
kCCOptionPKCS7Padding , [keyData bytes],
kCCKeySize3DES, [iv bytes],
[encryptData bytes], [encryptData length],
resultBuffer, resultBufferSize, &moved);
if (cryptorStatus == kCCSuccess) {
return [[NSData dataWithBytes:resultBuffer length:moved] base64EncodedStringWithOptions:0];
} else {
return nil;
}
}
iOS
-(NSString *)generate24ByteKeySameAsAndroidDotNet:(NSString *)key{
NSString *new24ByteKey = key;
;
new24ByteKey = [new24ByteKey stringByAppendingString:[key substringWithRange:NSMakeRange(0, 8)]];
return new24ByteKey;
}
As #Jugal Desai mentioned, the key difference between iOS and Android/.Net is that the later ones automatically fill the key (with size 16 bytes) with another 8 bytes from the start of the key! You saved me :)
Here I provide the simple fix in Swift 3:
....
YOUR_KEY_SIZE_16 = YOUR_KEY_SIZE_16 + YOUR_KEY_SIZE_16[0...7]
....
Sample complete code (MD5 for key hash + ECB + PKCS7Padding) with base64 result:
func tripleDesEncrypt(keyString: String, pass: String) -> String{
let keyData = keyString.data(using: .utf8)!
var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))
_ = digestData.withUnsafeMutableBytes {digestBytes in
keyData.withUnsafeBytes {messageBytes in
CC_MD5(messageBytes, CC_LONG(keyData.count), digestBytes)
}
}
digestData = digestData + digestData[0...7]
let data = pass.data(using: .utf8)!
let dataNS = data as NSData
let cryptData = NSMutableData(length: Int(dataNS.length) + kCCBlockSize3DES)!
let keyLength = size_t(kCCKeySize3DES)
let operation: CCOperation = UInt32(kCCEncrypt)
let algoritm: CCAlgorithm = UInt32(kCCAlgorithm3DES)
let options: CCOptions = UInt32(kCCOptionECBMode + kCCOptionPKCS7Padding)
var numBytesEncrypted :size_t = 0
let cryptStatus = CCCrypt(operation,
algoritm,
options,
(digestData as NSData).bytes,
keyLength,
nil,
dataNS.bytes,
dataNS.length,
cryptData.mutableBytes,
cryptData.length,
&numBytesEncrypted)
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.length = Int(numBytesEncrypted)
// Not all data is a UTF-8 string so Base64 is used
let base64cryptString = cryptData.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength76Characters)
return base64cryptString
} else {
print("Error: \(cryptStatus)")
}
return ""
}
How serious in decrypted?
- (NSString *)encrypt:(NSString *)encryptValue key:(NSString *)key24Byte IV:(NSString *)IV{
// first of all we need to prepare key
if([key length] != 24)
return #"Require 24 byte key, call function generate24ByteKeySameAsAndroidDotNet with a 16Byte key same as used in Android and .NET"; //temporary error message
NSData *keyData = [key24Byte dataUsingEncoding:NSUTF8StringEncoding];
// our key is ready, let's prepare other buffers and moved bytes length
NSData *encryptData = [encryptValue dataUsingEncoding:NSUTF8StringEncoding];
size_t resultBufferSize = [encryptData length] + kCCBlockSize3DES;
unsigned char resultBuffer[resultBufferSize];
size_t moved = 0;
// DES-CBC requires an explicit Initialization Vector (IV)
// IV - second half of md5 key
NSMutableData *ivData = [[IV dataUsingEncoding:NSUTF8StringEncoding]mutableCopy];
NSMutableData *iv = [NSMutableData dataWithData:ivData];
CCCryptorStatus cryptorStatus = CCCrypt(kCCEncrypt, kCCAlgorithm3DES,
kCCOptionPKCS7Padding , [keyData bytes],
kCCKeySize3DES, [iv bytes],
[encryptData bytes], [encryptData length],
resultBuffer, resultBufferSize, &moved);
if (cryptorStatus == kCCSuccess) {
return [[NSData dataWithBytes:resultBuffer length:moved] base64EncodedStringWithOptions:0];
} else {
return nil;
}
}