swift5xcode10.2

Java to swift conversion in cryptography


I have a java code, where i used cryptography( Encryption and decryption). I want to use the same process on swift code. //my java code for encryption in crypto class

class Crypto {

        private SecretKeySpec skc;
        private final static char[] hexArray = "0123456789abcdef".toCharArray();
        String TAG = "TAG";
        @RequiresApi(api = Build.VERSION_CODES.O)
        Crypto(String token) {
            try {
                MessageDigest md = MessageDigest.getInstance("MD5");
                byte[] thedigest = md.digest(("YourStrongKey" + token).getBytes("UTF-8"));


                skc = new SecretKeySpec(thedigest, "AES/ECB/PKCS5Padding");

            } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
  private 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;
        }

    public byte[] encrypt(String clear) throws Exception {
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, skc);
            byte[] input = clear.getBytes("utf-8");
            byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
            int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
            cipher.doFinal(cipherText, ctLength);
            Log.d(TAG, "encrypt: ciper: "+cipher.toString());
            return cipherText;
        }

        public byte[] decrypt(byte[] buf, int offset, int len) throws Exception {
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, skc);
            byte[] dec = new byte[cipher.getOutputSize(len - offset)];
            int ctLength = cipher.update(buf, offset, len - offset, dec, 0);
            cipher.doFinal(dec, ctLength);
            Log.d(TAG, "encrypt: dec: "+dec.toString());
            return dec;
        }
    }

//uses of java form main activity class

Crypto crypto;
String token = "Jpc3MiOiJodHRwczovL3NlY3VyZXRva2";
crypto = new Crypto(token);

JSONObject msg = new JSONObject();
msg.put("phoneData", "data1"); 
msg.put("countryData", "data2");

Log.d(TAG, "startHttpApiRequest: msg: "+msg);
String ss = msg.toString();
byte[] msgD = crypto.encrypt(ss);

//my swift code on playground

class SecretSpec {
    var algorithm: String = "AES/ECB/PKCS7padding"
    var key = [UInt8]()

    func secretSpec(key: [UInt8], algorithm: String){
        self.key = key
        self.algorithm = algorithm
    }

    func getAlgorithm() -> String {
        return self.algorithm
    }

    func getFormat() -> String {
        return "RAW"
    }

    func getEncoded() -> [UInt8] {
        return self.key
    }

    func hashCode() -> Int {
        var retval: Int = 0
        for i in 1...key.count-1 {
            retval = retval + Int(key[i]) * Int(i)
        }

        if (algorithm.lowercased() == "tripledes"){
            retval = retval ^ Int("desede".hashValue)
            return retval
        }else{
            retval = retval ^ Int(algorithm.lowercased().hashValue)
            return retval
        }
    }
}


extension String {
    func aesEncrypt(key: String) -> 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(kCCOptionECBMode + kCCOptionPKCS7Padding)

            var numBytesEncrypted :size_t = 0

            let cryptStatus = CCCrypt(operation, algoritm, options,
                                      (keyData as NSData).bytes,keyLength,nil, (data as NSData).bytes, data.count,cryptData.mutableBytes, cryptData.length,&numBytesEncrypted)

            if UInt32(cryptStatus) == UInt32(kCCSuccess) {
                cryptData.length = Int(numBytesEncrypted)

                var bytes = [UInt8](repeating: 0, count: cryptData.length)
                cryptData.getBytes(&bytes, length: cryptData.length)

                var hexString = ""
                for byte in bytes {
                    hexString += String(format:"%02x", UInt8(byte))
                }
                return hexString
            }
            else {
                return nil
            }
        }
        return nil
    }
}

func MD5(_ string: String) -> String? {
    let length = Int(CC_MD5_DIGEST_LENGTH)
    var digest = [UInt8](repeating: 0, count: length)

    if let d = string.data(using: String.Encoding.utf8) {
        _ = d.withUnsafeBytes { (body: UnsafePointer<UInt8>) in
            CC_MD5(body, CC_LONG(d.count), &digest)
        }
    }

    return (0..<length).reduce("") {
        $0 + String(format: "%02x", digest[$1])
    }
}



var token = "YourStrongKeyJpc3MiOiJodHRwczovL3NlY3VyZXRva2"    
var algorithm: String = "AES/ECB/PKCS5padding"    
var tokenRes = MD5(token)    
let digest = hexstringToBytes(tokenRes!)
var skcSpec = SecretSpec()
skcSpec.secretSpec(key: digest!, algorithm: algorithm)
print("hello: \(skcSpec)")

let msg: NSMutableDictionary = NSMutableDictionary()    
msg.setValue("data1", forKey: "phoneData")
msg.setValue("data2", forKey: "countryData")

let msgData: NSData
var msgStr: String = ""
var requestUrl: String = ""

do {
    msgData = try JSONSerialization.data(withJSONObject: msg, options: JSONSerialization.WritingOptions()) as NSData
    msgStr = NSString(data: msgData as Data, encoding: String.Encoding.utf8.rawValue)! as String

} catch _ {
    print ("JSON Failure")
}
var str = skcSpec.getEncoded()
print(str)

let skc = NSString(bytes: str, length: str.count, encoding: String.Encoding.ascii.rawValue)! as String

let eneMsg = msgStr.aesEncrypt(key: skc)!
print("encoded: \(eneMsg)")

it does not gives me the same output. please help me to finding same output. nb: java code is fixed for encryption and decryption.

Outputs:

in java: ffe957f00bdd93cfe1ef1133993cc9d2d8682310c648633660b448d92098e7fa07ae25f600467894ac94ccdcbe4039b8

in swift: 2e130be30aa3d8ff7fdc31dc8ffe1c39afe987ccbf8481caed9c78b49624a31c68df63a899df130128af6852c82d9aea


Solution

  • I have tried to convert your Crypto class directly into Swift, and got this:

    import Foundation
    import CommonCrypto
    
    func MD5(_ data: Data) -> Data {
        let length = Int(CC_MD5_DIGEST_LENGTH)
        var digest = Data(count: length)
    
        data.withUnsafeBytes {bytes in
            _ = digest.withUnsafeMutableBytes {mutableBytes in
                CC_MD5(bytes.baseAddress, CC_LONG(bytes.count), mutableBytes.bindMemory(to: UInt8.self).baseAddress)
            }
        }
    
        return digest
    }
    
    func hexStringToData(_ s: String) -> Data {
        let len = s.count
        var data = Data(count: len/2)
        var start = s.startIndex
        for i in 0..<len/2 {
            let end = s.index(start, offsetBy: 2)
            data[i] = UInt8(s[start..<end], radix: 16)!
            start = end
        }
        return data
    }
    
    class Crypto {
    
        private var key: Data
    
        init(_ token: String) {
            let theDigest = MD5(("YourStrongKey" + token).data(using: .utf8)!)
    
            key = theDigest
    
        }
    
        public func encrypt(_ clear: String) -> Data? {
            let input = clear.data(using: .utf8)!
            var cipherText = Data(count: input.count + kCCBlockSizeAES128)
            let keyLength = size_t(kCCKeySizeAES128)
            assert(key.count >= keyLength)
            let operation: CCOperation = UInt32(kCCEncrypt)
            let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
            let options: CCOptions = UInt32(kCCOptionECBMode + kCCOptionPKCS7Padding)
    
            var ctLength: size_t = 0
    
            let cryptStatus = key.withUnsafeBytes {keyBytes in
                input.withUnsafeBytes {inputBytes in
                    cipherText.withUnsafeMutableBytes {mutableBytes in
                        CCCrypt(operation, algoritm, options,
                                keyBytes.baseAddress, keyLength, nil,
                                inputBytes.baseAddress, inputBytes.count,
                                mutableBytes.baseAddress, mutableBytes.count,
                                &ctLength)
                    }
                }
            }
            if cryptStatus == CCCryptorStatus(kCCSuccess) {
                cipherText.count = Int(ctLength)
                return cipherText
            } else {
                return nil
            }
        }
    
        public func decrypt(_ buf: Data, _ offset: Int, _ len: Int) -> Data? {
            var dec = Data(count: len)
            let keyLength = size_t(kCCKeySizeAES128)
            let operation: CCOperation = UInt32(kCCDecrypt)
            let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
            let options: CCOptions = UInt32(kCCOptionECBMode + kCCOptionPKCS7Padding)
    
            var ctLength :size_t = 0
    
            let cryptStatus = key.withUnsafeBytes {keyBytes in
                buf.withUnsafeBytes {inputBytes in
                    dec.withUnsafeMutableBytes {mutableBytes in
                        CCCrypt(operation, algoritm, options,
                                keyBytes.baseAddress, keyLength, nil,
                                inputBytes.baseAddress, inputBytes.count,
                                mutableBytes.baseAddress, mutableBytes.count,
                                &ctLength)
                    }
                }
            }
            if cryptStatus == CCCryptorStatus(kCCSuccess) {
                dec.count = Int(ctLength)
                return dec
            } else {
                return nil
            }
        }
    }
    let token = "Jpc3MiOiJodHRwczovL3NlY3VyZXRva2"
    let crypto = Crypto(token)
    
    let msg = [
        "phoneData": "data1",
        "countryData": "data2",
    ]
    let msgData = try! JSONSerialization.data(withJSONObject: msg)
    
    let ss = String(data: msgData, encoding: .utf8)!
    print(ss) //->{"phoneData":"data1","countryData":"data2"}
    //### Be careful, this may be different than the result of JSONObject#toString() in Java.
    //### Sometimes, outputs->{"countryData":"data2","phoneData":"data1"}
    
    let msgD = crypto.encrypt(ss)!
    print(msgD as NSData) //-><ffe957f0 0bdd93cf e1ef1133 993cc9d2 24e8b9a1 162520e5 54a3d8af 8f478db7 07ae25f6 00467894 ac94ccdc be4039b8>
    //### When `ss` is {"phoneData":"data1","countryData":"data2"}
    
    let decrypted = crypto.decrypt(msgD, 0, msgD.count)!
    print(String(data: decrypted, encoding: .utf8)!) //->{"phoneData":"data1","countryData":"data2"}
    

    In your Java code, your key String ("YourStrongKey" + token) gets two conversions until used as a binary key data for AES:

     key: "YourStrongKey" + token
     ↓
     UTF-8 bytes
     ↓
     MD5 digest
    

    But, in your Swift code, you are converting the key more times:

     token: "YourStrongKeyJpc3MiOiJodHRwczovL3NlY3VyZXRva2"
     ↓
     UTF-8 bytes (`d` in `MD5(_:)`)
     ↓
     MD5 digest
     ↓
     binary to HEX String conversion (return value from `MD5(_:)`)
     ↓
     UTF-8 bytes (`keyData` in `aesEncrypt(key:)`)
    

    There's no binary to HEX String conversion in your Java code and you should have not done that.


    By the way, ECB is not considered to be a safe way to encrypt, you should better update your Java code.