SWIFT AES加密和解密——获取不同的结果

4
我正在实现Swift中的AES加密。Java和C#的加密解密正常工作。
在Swift中,我得到了与实际结果不同的结果。调试时,我注意到Java默认使用有符号整数。所以我采用了同样的方式,在这种情况下,我能够验证两个应用程序(Java和Swift)中的derivedKey是相同的。
但在创建keyData和ivData时,它丢失了有符号数据。不确定是否会导致问题。
我尝试了在AES Encryption .net to swift中解释的以下代码。
func decrypt(encryptedText: String, keys :String)  -> String{
    let encryptedData = encryptedText.data(using: .utf16LittleEndian)
    let derivedKey = generateDerivedKey(keyString: keys)
    let key = Array(derivedKey[0..<32])
    let iv = Array(derivedKey[32..<48])   
    let keyData = Data(bytes: key, count: key.count)
    let ivData = Data(bytes: iv, count: iv.count)        
    let decryptedData = testDeCrypt(data: encryptedData!, keyData: keyData, ivData: ivData, operation: kCCDecrypt)      

    return String(bytes: decryptedData, encoding: .unicode)!
}

func generateDerivedKey(keyString :String) -> [Int8] {
    let salt: [UInt8] = [0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76]
    var key = [UInt8](repeating: 0, count: 48)
    CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), keyString, keyString.utf8.count, salt, salt.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), 1000, &key, 48)
    let derivedKey : [Int8] = key.map {Int8(bitPattern: $0)}

    return derivedKey
}

func testDeCrypt(data: Data, keyData: Data, ivData: Data, operation: Int) -> Data {
    assert(keyData.count == Int(kCCKeySizeAES128) || keyData.count == Int(kCCKeySizeAES192) || keyData.count == Int(kCCKeySizeAES256))
    var decryptedData = Data(count: data.count)
    var num_bytes_decrypted: size_t = 0
    let operation = CCOperation(operation)
    let algoritm = CCAlgorithm(kCCAlgorithmAES)
    let options = CCOptions(kCCOptionPKCS7Padding)  
    let decryptedDataCount = decryptedData.count
    let cryptoStatus = keyData.withUnsafeBytes {keyDataBytes in
        ivData.withUnsafeBytes {ivDataBytes in
            data.withUnsafeBytes {dataBytes in
                decryptedData.withUnsafeMutableBytes {decryptedDataBytes in
                    CCCrypt(operation, algoritm, options, keyDataBytes, keyData.count, ivDataBytes, dataBytes, data.count, decryptedDataBytes, decryptedDataCount, &num_bytes_decrypted)
                }
            }
        }
    }      
    if cryptoStatus == CCCryptorStatus(kCCSuccess) {
        decryptedData.count = num_bytes_decrypted
        return decryptedData
    } else {
        return Data()
    }
}

Java 代码

public static String aesDecrypt(String text, String key) {
    byte[] decValue = null;
    try {
        byte[] salt = new byte[] { 0x49, 0x76, 0x61, 0x6E, 0x20, 0x4D,
                0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 };
        SecretKeyFactory factory = SecretKeyFactory
                .getInstance("PBKDF2WithHmacSHA1");
        PBEKeySpec pbeKeySpec = new PBEKeySpec(key.toCharArray(), salt,
                1000, 384);

        Key secretKey = factory.generateSecret(pbeKeySpec);
        byte[] keys = new byte[32];
        byte[] iv = new byte[16];
        System.arraycopy(secretKey.getEncoded(), 0, keys, 0, 32);
        System.arraycopy(secretKey.getEncoded(), 32, iv, 0, 16);

        SecretKeySpec secretSpec = new SecretKeySpec(keys, "AES");

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        try {
            cipher.init(Cipher.DECRYPT_MODE, secretSpec, ivSpec);
        } catch (InvalidKeyException e) {

        } catch (InvalidAlgorithmParameterException e) {
        }

        org.apache.commons.codec.binary.Base64 decoder = new org.apache.commons.codec.binary.Base64();
        byte[] decodedValue = decoder.decode(text.getBytes());

        decValue = cipher.doFinal(decodedValue);

    } catch (Exception e) {
    }

    if (decValue != null) {
        return new String(decValue, Charset.forName("UTF_16LE"));
    } else {
        return null;
    }
}

测试数据 密钥:"ThisIsATestPassword444Encryption" 文本:"TestStringToEncrypt"

Java输出 编码后的密文:[97,47,77,79,118,111,79,70,47,87,90,67,81,98,51,74,83,88,97,68,84,105,72,71,67,121,122,86,81,116,106,104,117,78,108,118,49,48,65,77,69,53,114,43,120,104,89,120,50,98,80,66,50,77,87,80,103,110,117,118,118,97,78,106]

加密后的文本:a/MOvoOF/WZCQb3JSXaDTiHGCyzVQtjhuNlv10AME5r+xhYx2bPB2MWPgnuvvaNj

解密文本.getBytes:[97,47,77,79,118,111,79,70,47,87,90,67,81,98,51,74,83,88,97,68,84,105,72,71,67,121,122,86,81,116,106,104,117,78,108,118,49,48,65,77,69,53,114,43,120,104,89,120,50,98,80,66,50,77,87,80,103,110,117,118,118,97,78,106]

解码解密文本:[107,-13,14,-66,-125,-123,-3,102,66,65,-67,-55,73,118,-125,78,33,-58,11,44,-43,66,-40,-31,-72,-39,111,-41,64,12,19,-102,-2,-58,22,49,-39,-77,-63,-40,-59,-113,-126,123,-81,-67,-93,99]

Swift输出: 加密文本:a/MOvoOF/WZCQb3JSXaDTiHGCyzVQtjhuNlv10AME5r+xhYx2bPB2MWPgnuvvaNj

解密文本:۽瑒왿᪰߆牷ྐྵ䐫徺ꋴ鐧ݐ斑ﷃ翴㦦જ㤉ꄕ䞴櫘勐鼍ᐏ┓ീ學䥏㿖칵鬥솽ᢼ铡鴷⤃ꗞ䛂䋗쿠蒻⯨䍊䂷篥럟⤫俷違둘๔Ꞵ‵

Swift和Java加密匹配。

非常感谢您的帮助。


text.getBytes()是什么? - OOPer
你最好将这些信息包含在你的问题文本中。通过更新你的代码,有人会更容易地找到你的问题。 - OOPer
再说一遍,没有密钥读者无法测试代码。创建一个可以公开的样本密钥,并使用该密钥生成一个样本加密数据。请向我们展示样本加密文本和样本密钥。请不要忘记将它们包含在您的问题文本中。 - OOPer
@OOPer,很抱歉我在编辑实际代码以发布时有一个Java代码拼写错误。正确的应该是 System.arraycopy(secretKey.getEncoded(), 0, keys, 0, 32); 而不是 System.arraycopy(secretKey.getEncoded(), 0, key, 0, 32); 我已经进行了更正,现在你应该可以无问题地进行Java解密操作了。 - SJMC
抱歉,我误解了PBEKeySpec的细节,问题可能不是我上次评论中提到的那个问题。 - OOPer
显示剩余3条评论
2个回答

2
你的Swift代码中最糟糕的两个部分是:
#1
let encryptedData = encryptedText.data(using: .utf16LittleEndian)

and:

#2

return String(bytes: decryptedData, encoding: .unicode)!

#1

在您的Java代码中,您正在将text解码为Base-64,但在Swift代码中,您只是获取了.utf16LittleEndian的字节表示,这与Base-64无关。

您可能需要类似于以下内容:

guard let encryptedData = Data(base64Encoded: encryptedText) else {
    print("Data is not a valid base-64")
    return nil
}

(你的decrypt(encryptedText:keys:)应该返回String?而不是String,因为解密可能会失败。)


#2

在你的Java代码中,你使用new String(decValue, Charset.forName("UTF_16LE"))将解密后的字节转换为字符串。UTF_16LE代表UTF-16小端。在Swift的String.Encoding中,相当于utf16LittleEndian

这一行应该如下:

return String(bytes: decryptedData, encoding: .utf16LittleEndian)

你的 generateDerivedKey(keyString:) 方法可以被简化,当你使用 [UInt8] 作为其返回类型时。(在 Swift 中最好使用 UInt8 表示中间字节类型。)

所有这些事情综合起来,你的 Swift 代码应该是:

func decrypt(encryptedText: String, keys: String)  -> String? { //### `String?` rather than `String`
    //### Decode `encryptedText` as Base-64
    guard let encryptedData = Data(base64Encoded: encryptedText) else {
        print("Data is not a valid Base-64")
        return nil
    }
    let derivedKey = generateDerivedKey(keyString: keys)
    //### A little bit shorter, when `derivedKey` is of type `[UInt8]`
    let keyData = Data(bytes: derivedKey[0..<32])
    let ivData = Data(bytes: derivedKey[32..<48])
    if let decryptedData = testDeCrypt(data: encryptedData, keyData: keyData, ivData: ivData, operation: kCCDecrypt) {
        //### Use `utf16LittleEndian`
        return String(bytes: decryptedData, encoding: .utf16LittleEndian)
    } else {
        //### return nil, when `testDeCrypt` fails
        return nil
    }
}

func generateDerivedKey(keyString: String) -> [UInt8] { //### `[UInt8]`
    let salt: [UInt8] = [0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76]
    var key = [UInt8](repeating: 0, count: 48)
    CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), keyString, keyString.utf8.count, salt, salt.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), 1000, &key, 48)

    //### return the Array of `UInt8` directly
    return key
}

func testDeCrypt(data: Data, keyData: Data, ivData: Data, operation: Int) -> Data? { //### make it Optional
    assert(keyData.count == Int(kCCKeySizeAES128) || keyData.count == Int(kCCKeySizeAES192) || keyData.count == Int(kCCKeySizeAES256))
    var decryptedData = Data(count: data.count)
    var numBytesDecrypted: size_t = 0
    let operation = CCOperation(operation)
    let algoritm = CCAlgorithm(kCCAlgorithmAES)
    let options = CCOptions(kCCOptionPKCS7Padding)
    let decryptedDataCount = decryptedData.count
    let cryptoStatus = keyData.withUnsafeBytes {keyDataBytes in
        ivData.withUnsafeBytes {ivDataBytes in
            data.withUnsafeBytes {dataBytes in
                decryptedData.withUnsafeMutableBytes {decryptedDataBytes in
                    CCCrypt(operation, algoritm, options, keyDataBytes, keyData.count, ivDataBytes, dataBytes, data.count, decryptedDataBytes, decryptedDataCount, &numBytesDecrypted)
                }
            }
        }
    }
    if cryptoStatus == CCCryptorStatus(kCCSuccess) {
        decryptedData.count = numBytesDecrypted
        return decryptedData
    } else {
        return nil //### returning `nil` instead of `Data()`
    }
}

使用上述新的Swift代码,我可以生成与您的Java代码相同的结果:
let test = "a/MOvoOF/WZCQb3JSXaDTiHGCyzVQtjhuNlv10AME5r+xhYx2bPB2MWPgnuvvaNj"
let keys = "ThisIsATestPassword444Encryption"

if let result = decrypt(encryptedText: test, keys: keys) {
    print(result) //->TestStringToEncrypt
} else {
    print("*Cannot decrypt*")
}

(我需要更新我的旧Java环境,以便比较Java和Swift之间的中间结果,但这是另一件事情...)


工作得很好。非常感谢OOper提供详细的解释和快速的解决方案。我真的很感激你的帮助!!! - SJMC
@SJMC,我本可以根据你对我的评论的回复来撰写答案(你知道其中有些不清楚并且存在误解)。你是这个答案的共同作者。 - OOPer

0

我曾经遇到过同样的问题,通过检查缓冲区 uint 数组中的第一项是否为“0x00”来解决了这个问题。如果不是,则在索引 0 处添加“0x00”。 以下是接收加密数据并在解密后再次发送的示例:

    let rsaKeyValue = xmlRep["RSAKeyValue"]
    let modulus = rsaKeyValue["Modulus"].element?.text
    let exponent = rsaKeyValue["Exponent"].element?.text


    var modBuffer: [UInt8] = [UInt8](Data(base64Encoded: modulus!)!)
    let expBuffer: [UInt8] = [UInt8](Data(base64Encoded: exponent!)!)

    if let prefix = modBuffer.first, prefix != 0x00 {
        modBuffer.insert(0x00, at: 0)
    }



    let modulusEncoded: [UInt8] = modBuffer.encodeAsInteger()
    let exponentEncoded: [UInt8] = expBuffer.encodeAsInteger()

    let sequenceEncoded: [UInt8] = (modulusEncoded + exponentEncoded).encodeAsSequence()

    let keyData = Data(bytes: sequenceEncoded)

    let keySize = (modBuffer.count * 8)

    let attributes: [String: Any] = [
        kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
        kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
        kSecAttrKeySizeInBits as String: keySize,
        kSecAttrIsPermanent as String: false
    ]

    var err : Unmanaged<CFError>?
    let publicKey = SecKeyCreateWithData(keyData as CFData, attributes as CFDictionary, &err)

    guard let tokenData = Authentication.getUserToken()?.data(using: .utf8)  else { return }


    let chunks = tokenData.toUInt8Array().chunked(into: 200)
    var encryptedChunks = [[UInt8]]()

    for chunk in chunks
    {
        var encryptionError: Unmanaged<CFError>?

        let cipher = SecKeyCreateEncryptedData(publicKey!, .rsaEncryptionPKCS1, Data(bytes: chunk) as CFData, &encryptionError)
        encryptedChunks.append([UInt8](cipher! as Data))
    }


    var str = "["
    for chunk in encryptedChunks {
        for byte in chunk {
            str.append("\(byte),")
        }
        str.remove(at: String.Index(encodedOffset: str.count - 1))
        str.append(";")
    }
    str.append("]")
    let finalStr = str.replacingOccurrences(of: ";]", with: "]")

这里是用于在Swift中进行加密的扩展程序

internal extension Array where Element == UInt8 {

func encodeAsInteger() -> [UInt8] {
    var tlvTriplet: [UInt8] = []
    tlvTriplet.append(0x02)
    tlvTriplet.append(contentsOf: lengthField(of: self))
    tlvTriplet.append(contentsOf: self)

    return tlvTriplet
}

func encodeAsSequence() -> [UInt8] {
    var tlvTriplet: [UInt8] = []
    tlvTriplet.append(0x30)
    tlvTriplet.append(contentsOf: lengthField(of: self))
    tlvTriplet.append(contentsOf: self)

    return tlvTriplet
}

func chunked(into size: Int) -> [[Element]] {
    return stride(from: 0, to: count, by: size).map {
        Array(self[$0 ..< Swift.min($0 + size, count)])
    }
}

}


1
谢谢您的建议。我想知道我是否可以在我的代码中使用您的解决方案,因为两者是不同的算法。如果我有误,请纠正我。 - SJMC
Swift对编码有不同的方式,我真的不知道为什么,做了很多搜索,但是没有任何结果。这与加密或解密无关,而是关于Swift如何读取字节数组。它应该以“0x00”开头,如果不是,则必须插入索引0。 - Hussam
对于算法,它将影响数据的加密和解密,请确保您发送和接收的数据以正确的方式进行,例如安卓。 - Hussam
我尝试在Java中模仿相同的代码,但没有成功 :( - SJMC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接