1

I am working on a static library that takes an input String, encrypts it with the private key then returns it. (Because the client request to use private key on the mobile side.)

I am modifying code from this post and this post to work with the private key, but the result string always returns nil.

Key for testing:

let privateTestingKey = "-----BEGIN RSA PRIVATE KEY-----MIICXwIBAAKBgQDPJfl0gC95kB3sS0SOfXW45UooiYjT3ZeHRDn/eG+So09x7cXLVbRILxxXfaU+9b5Yw2wETMvuzhP8C5Qd5PQy5lkH5LjcqJjDTvOBuBxNOSY4rmlqdy/skqM4FHjTGdwI2nZiVkjqOFXM+XZ94Ar+pszJifbSPElR1pO4NfT76QIDAQABAoGBAJjtoRdoFyR4yA6NlsRXTRS+ehwpRVGcc2TScrrvL/ejB2DFuFOgJyNvXE4fHWK4y9j+FP2rsJbRnyFhbu0O/VRNPa5Llnejea+5ov/3wxFqiae7pn6bjmiW/xdy0ycLWo90wLX9QqYjKBHomnh15FIajmSuRaUlBaL6DiZt010tAkEA0zLTPiH35meyXim7I7+mMmAN68zGu3cYpqq8i/xlHvXjWrBDV5saxRDsm97ktFqiRpvIoV3n0ZoT+3xjKhGINwJBAPsXM9hWdmWUXmaaAnXZDmKbNltRSZaaz4BktGRUJM0grt7kMndfBgd2JENNzYTfLpOWMIfHXpW9T4pVCjg5TN8CQQDGZf91ZbmYUx+HP5KSUY4R0pQhR/wEzSt2HfwDUPW5cOnEHsMUQBuUtoJfJrMYDfBVfjCqDiogh6pv2/jX4yJfAkEAqwsvQhwEI0Zi2DnpmyX1aq6Y5LQHERT8bVYsnHvFZgbxmNySlEai8MpGAaMqcW0naVpSTOw/PnnriSxM/efquQJBAM/3eRqzBHp/v3bRM+npSgEOaeOmtNiz+oNrJ09mUToyDTkuFOg7J9ojNkeviR4xqs9Hoos8lP3ho1uVcE3/tRQ=-----END RSA PRIVATE KEY-----"

Code for RSA encryption:

import Foundation
import Security

public class MPEncryption {
  var publicKey: SecKey!
  
  public init(publicKeyString: String) throws {
    self.publicKey = try makePublicKey(from: publicKeyString)
  }
  
  private func makePublicKey(from keyString: String) throws -> SecKey {
    let pubKeyData = Data(base64Encoded: sanitize(key: keyString))!
    
    let query: [String: Any] = [
      kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
      kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
      kSecAttrKeySizeInBits as String: 1024
    ]
    
    var unmanagedError: Unmanaged<CFError>?
    
    guard let pubKeyRef = SecKeyCreateWithData(pubKeyData as CFData, query as CFDictionary, &unmanagedError) else {
      throw unmanagedError!.takeRetainedValue() as Error
    }
    
    return pubKeyRef
  }
  
  public func encrypt(value: String) throws -> String {
    let valueData = value.data(using: .utf8)!
    
    let bufferSize = SecKeyGetBlockSize(publicKey) - 11
    let buffers = makeBuffers(fromData: valueData, bufferSize: bufferSize)
    
    var encryptedData = Data()
    
    for buffer in buffers {
      var encryptionError: Unmanaged<CFError>?
      
      guard let encryptedBuffer = SecKeyCreateEncryptedData(publicKey, .rsaEncryptionPKCS1, buffer as CFData, &encryptionError) as Data? else {
        throw encryptionError!.takeRetainedValue() as Error
      }
      
      encryptedData.append(encryptedBuffer)
    }
    
    return encryptedData.base64EncodedString()
  }
  
  private func makeBuffers(fromData data: Data, bufferSize: Int) -> [Data] {
    guard data.count > bufferSize else {
      return [data]
    }
    
    var buffers: [Data] = []
    
    for i in 0..<bufferSize {
      let start = i * bufferSize
      
      let lengthOffset = start + bufferSize
      let length = lengthOffset < data.count ? bufferSize : data.count - start
      
      let bufferRange = Range<Data.Index>(NSMakeRange(start, length))!
      buffers.append(data.subdata(in: bufferRange))
    }
    
    return buffers
  }
  
  private func sanitize(key: String) -> String {
    let headerRange = key.range(of: "-----BEGIN RSA PRIVATE KEY-----")
    let footerRange = key.range(of: "-----END RSA PRIVATE KEY-----")
    
    var sanitizedKey = key
    
    if let headerRange = headerRange, let footerRange = footerRange {
      let keyRange = Range<String.Index>(uncheckedBounds: (lower: headerRange.upperBound, upper: footerRange.lowerBound))
      sanitizedKey = String(key[keyRange])
    }
    
    return sanitizedKey
      .trimmingCharacters(in: .whitespacesAndNewlines)
      .components(separatedBy: "\n")
      .joined()
  }
}

I can't locate the problem, any suggestion is appreciated.

Bryce Chan
  • 1,639
  • 11
  • 26

1 Answers1

3

When the encrypt() method is called, the following error message is displayed on my machine:

encrypt:RSA:PKCS1: algorithm not supported by the key <SecKeyRef algorithm id: 1, key type: RSAPrivateKey, ...>

This is not surprising, since the code tries to encrypt with a private key (labeled in the code as public key, which is misleading!). However, a private key is applied only for signing, while a public key is used for encryption.

Encryption with the private key is not consistently implemented by libraries. Some extract the public key from the private key and encrypt with the public key, some create a signature with the private key and others do not support this at all (like this library here).

It seems most likely to me that your requirement is to create a signature. Technically, encryption and signing are similar and differ in that encryption uses the public key for modular exponentiation and signing uses the private key. In addition, the padding variants differ. However, the purposes are completely different; encryption is about confidentiality, while signing is about verifying authenticity.

So you could try signing instead of encrypting. For this, the code has to be changed only slightly (however, sooner or later the variable names should also be adapted):

guard let encryptedBuffer = SecKeyCreateSignature(publicKey, .rsaSignatureDigestPKCS1v15Raw, buffer as CFData, &encryptionError) as Data? else {
  throw encryptionError!.takeRetainedValue() as Error
}

encryptedData now contains the signature (and publicKey denotes the private key).

Note that for rsaSignatureDigestPKCS1v15Raw the message is signed directly, i.e. without hashing or considering the digest ID. Since no digest ID is specified, this might be the correct variant.

However, be aware that the message length is limited (analogous to encryption). Compliant with the standard is a hashing and the consideration of the digest ID, which is achieved e.g. with rsaSignatureMessagePKCS1v15SHA256 for SHA-256.

Topaco
  • 40,594
  • 4
  • 35
  • 62