diff --git a/python/src/aes256.py b/python/src/aes256.py index 5c76fee..bb5f1b7 100644 --- a/python/src/aes256.py +++ b/python/src/aes256.py @@ -66,7 +66,9 @@ def __pkcs5_padding(self, s): @type s: string @rtype: string """ - s = s + (self.BLOCK_SIZE - len(s) % self.BLOCK_SIZE) * chr(self.BLOCK_SIZE - len(s) % self.BLOCK_SIZE) + # getting the length after encoding ensures that characters such as © & 😀 are safe + length_of_s = len(s.encode('utf-8')) + s = s + (self.BLOCK_SIZE - length_of_s % self.BLOCK_SIZE) * chr(self.BLOCK_SIZE - length_of_s % self.BLOCK_SIZE) if sys.version_info[0] == 2: return s return bytes(s, 'utf-8') diff --git a/swift/AES256.swift b/swift/AES256.swift new file mode 100644 index 0000000..a32137d --- /dev/null +++ b/swift/AES256.swift @@ -0,0 +1,195 @@ +// +// AES256.swift +// DecryprionTest +// +// Created by Andrew Yaniv on 3/29/19. +// Copyright © 2019 Project Core Inc. All rights reserved. +// + +import Foundation +import CommonCrypto + +class AES256 { + + func encrypt(input: String, password: String) throws -> String { + + let data = input.data(using: .utf8)! + + let passwordData = password.data(using: .utf8)! + + let salt = randomString(length: 8).data(using: .utf8)! + + let key: Data = derivateKey(passphrase: passwordData, salt: salt) + + let iv = key[32...47] + + let outputLength = data.count + kCCBlockSizeAES128 + var outputBuffer = Array(repeating: 0, count: outputLength) + var numBytesEncrypted = 0 + + let status = CCCrypt(CCOperation(kCCEncrypt), + CCAlgorithm(kCCAlgorithmAES), + CCOptions(kCCOptionPKCS7Padding), + Array(key), + kCCKeySizeAES256, + Array(iv), + Array(data), + data.count, + &outputBuffer, + outputLength, + &numBytesEncrypted) + + guard status == kCCSuccess else { + throw EncryptionError.encryptionFailed(status: status) + } + + + let salted = Data("Salted__".utf8) + + let saltedWithSalt = salted + salt + + let outputBytes = saltedWithSalt + outputBuffer.prefix(numBytesEncrypted) + + let encrypted: String = outputBytes.base64EncodedString() + + return encrypted + } + + func decrypt(input: String, password: String) throws -> String { + + var inputData = Data(base64Encoded: input)! + + if let salted = String(data: inputData[...7], encoding: .utf8) { + if salted != "Salted__" { + throw ValidationError.saltedNotFound + } + } + + let salt: Data = inputData[8...15] + + let passwordData = password.data(using: .utf8)! + + let key = derivateKey(passphrase: passwordData, salt: salt) + + let iv = key[32...47] + + let ivBlock = iv.prefix(kCCBlockSizeAES128) + let cipherTextBytes = inputData[16...] + + let cipherTextLength = cipherTextBytes.count + var outputBuffer = Array(repeating: 0, count: cipherTextLength) + var numBytesDecrypted = 0 + + let status = CCCrypt(CCOperation(kCCDecrypt), + CCAlgorithm(kCCAlgorithmAES), + CCOptions(kCCOptionPKCS7Padding), + Array(key), + kCCKeySizeAES256, + Array(ivBlock), + Array(cipherTextBytes), + cipherTextLength, + &outputBuffer, + cipherTextLength, + &numBytesDecrypted) + + + + guard status == kCCSuccess else { + throw EncryptionError.decryptionFailed(status: status) + } + + let outputBytes = outputBuffer.prefix(numBytesDecrypted) + + let outputData: Data = Data(outputBytes) + if let decryptedText: String = String(data: outputData, encoding: .utf8) { + return decryptedText + } else { + throw ValidationError.decryptionFailed + } + } + + private func derivateKey(passphrase: Data, salt: Data) -> Data { + var salted: Data = Data() + + var dxData = Data() + + while salted.count < 48 { + let data: Data = dxData + passphrase + salt + + dxData = md5(data) + + salted = salted + dxData + } + + return salted + } + + private func md5(_ inputData: Data) -> Data { + var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH)) + + _ = digestData.withUnsafeMutableBytes {digestBytes in + inputData.withUnsafeBytes {messageBytes in + CC_MD5(messageBytes, CC_LONG(inputData.count), digestBytes) + } + } + + return digestData + } + + private func randomString(length: Int) -> String { + let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + return String((0..) in + let charA = UInt8(UnicodeScalar("a").value) + let char0 = UInt8(UnicodeScalar("0").value) + + func itoh(_ value: UInt8) -> UInt8 { + return (value > 9) ? (charA + value - 10) : (char0 + value) + } + + let hexLen = count * 2 + let ptr = UnsafeMutablePointer.allocate(capacity: hexLen) + + for i in 0 ..< count { + ptr[i*2] = itoh((bytes[i] >> 4) & 0xF) + ptr[i*2+1] = itoh(bytes[i] & 0xF) + } + + return String(bytesNoCopy: ptr, + length: hexLen, + encoding: .utf8, + freeWhenDone: true) + } + } +}