swiftencryptionjwtrsajwe

RSA Encryption function in Swift 4 from Public Key string


My ultimate goal is to create a JWE string, given a public key for iOS.

To make things easier for myself, I've broken down my steps so most pressingly, I need to create an encrypted key using RSA encryption from a secret key and public key string.

I've tried a lot of things found here in stack overflow and other places of the internet. For various reasons they just haven't worked out.

I am being guided by some Objective C code:

/* Device Data encryption - create JWE given DS publicKey */ +(NSString *)createJWE:(NSString *)payload withPublicKey:(SecKeyRef)publicKey {
// create secretKey for encryption
NSData *secret = [self generateRandom:(KEY_SIZE*2)];
NSData *hmacKey = [secret subdataWithRange:NSMakeRange(0, KEY_SIZE)]; NSData *aesKey = [secret subdataWithRange:NSMakeRange(KEY_SIZE,
KEY_SIZE)];
  NSData *iv = [self generateRandom: IV_SIZE];
// create header
NSString *header = @"{\"enc\":\"A128CBC-HS256\",\"alg\":\"RSA-OAEP\"}";
// encrypt secretKey
NSData *encryptedKey = [self rsaEncrypt:secret key:publicKey];
  // encrypt payload
NSData *encrypted = [self aesEncrypt:[payload dataUsingEncoding:NSUTF8StringEncoding] withKey:aesKey withIV:iv];
NSString *basePayload = [encrypted unpaddedBase64URLEncoded];
NSString *baseCEK = [encryptedKey unpaddedBase64URLEncoded];
NSString *baseHeader = [[header dataUsingEncoding:NSUTF8StringEncoding]
unpaddedBase64URLEncoded];
NSString *baseIV = [iv unpaddedBase64URLEncoded];
// create auth hash
NSData *hmac = [self hmac: encrypted withKey: hmacKey withIV: iv withA:
[baseHeader dataUsingEncoding:NSASCIIStringEncoding]];
return [NSString stringWithFormat:@"%@.%@.%@.%@.%@", baseHeader, baseCEK, baseIV, basePayload, [[self hmacToTag: hmac] unpadded

I am currently at this point of the function:

NSData *encryptedKey = [self rsaEncrypt:secret key:publicKey];

From this I have made the assumption that there is an rsaEncrypt function that accepts a secret and public key.

The public key I have access to looks like this:

let publicKey = """
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEChqe80lJLTTkJD3X3Lyd7Fj+
zuOhDZkjuLNPog3YR20e5JcrdqI9IFzNbACY/GQVhbnbvBqYgyql8DfPCGXpn0+X
NSxELIUw9Vh32QuhGNr3/TBpechrVeVpFPLwyaYNEk1CawgHCeQqf5uaqiaoBDOT
qeox88Lc1ld7MsfggQIDAQAB
-----END PUBLIC KEY-----
"""

Examples found on the internet handles the public key as a SecKey type. This is my first hurdle to performing the encryption.

  1. How do I convert the string to a SecKey, if it must be converted?
  2. What should my rsaEncrypt function do?

I expect the output to be an encrypted key after performing the encryption.

Update: Another variation, One public key sent also looks like this:

{ "kty": "RSA", "kid": "UUIDkeyidentifierforDS", "use": "enc", "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O- XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL- QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe- OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0F Vbb9k3- tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5i aiQkqgc5gHdrNP5zw", "e": "AQAB" }

Solution

  • You can do like this way...

    static func encrypt(string: String, publicKey: String?) -> String? {
            guard let publicKey = publicKey else { return nil }
            
            let keyString = publicKey.replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----\n", with: "").replacingOccurrences(of: "\n-----END PUBLIC KEY-----", with: "")
            guard let data = Data(base64Encoded: keyString) else { return nil }
            
            var attributes: CFDictionary {
                return [kSecAttrKeyType         : kSecAttrKeyTypeRSA,
                        kSecAttrKeyClass        : kSecAttrKeyClassPublic,
                        kSecAttrKeySizeInBits   : 2048,
                        kSecReturnPersistentRef : kCFBooleanTrue] as CFDictionary
            }
            
            var error: Unmanaged<CFError>? = nil
            guard let secKey = SecKeyCreateWithData(data as CFData, attributes, &error) else {
                print(error.debugDescription)
                return nil
            }
            return encrypt(string: string, publicKey: secKey)
        }
        
        private static func encrypt(string: String, publicKey: SecKey) -> String? {
            let buffer = [UInt8](string.utf8)
            
            var keySize   = SecKeyGetBlockSize(publicKey)
            var keyBuffer = [UInt8](repeating: 0, count: keySize)
            
            // Encrypto  should less than key length
            guard SecKeyEncrypt(publicKey, SecPadding.PKCS1, buffer, buffer.count, &keyBuffer, &keySize) == errSecSuccess else { return nil }
            return Data(bytes: keyBuffer, count: keySize).base64EncodedString()
        }