I'm trying to port this Java code to Swift to create a signature from private key.
The private key is generated using these instructions.
https://walmart.io/key-tutorial
openssl genrsa -des3 -out WM_IO_my_rsa_key_pair 2048
https://walmart.io/docs/affiliate/onboarding-guide
public String generateSignature(String key, String stringToSign) throws Exception {
Signature signatureInstance = Signature.getInstance("SHA256WithRSA");
ServiceKeyRep keyRep = new ServiceKeyRep(KeyRep.Type.PRIVATE, "RSA", "PKCS#8", Base64.decodeBase64(key));
PrivateKey resolvedPrivateKey = (PrivateKey) keyRep.readResolve();
signatureInstance.initSign(resolvedPrivateKey);
byte[] bytesToSign = stringToSign.getBytes("UTF-8");
signatureInstance.update(bytesToSign);
byte[] signatureBytes = signatureInstance.sign();
String signatureString = Base64.encodeBase64String(signatureBytes);
return signatureString;
}
This is my attempt, but it's not working. It fails at SecKeyCreateWithData
.
func createHash(string: String) -> Data {
let hash = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(CC_SHA256_DIGEST_LENGTH))
let hashLength = string.count
defer { hash.deallocate() }
CC_SHA256(string, CC_LONG(hashLength), hash)
return Data(bytes: hash, count: hashLength)
}
func createSignature(string: String) throws -> String? {
guard let url = Bundle.module.url(forResource: "WM_IO_private_key", withExtension: "pem") else {
return nil
}
let privateKey = try String(contentsOf: url, encoding: .utf8)
.replacingOccurrences(of: "-----BEGIN PRIVATE KEY-----", with: "")
.replacingOccurrences(of: "-----END PRIVATE KEY-----", with: "")
.split(separator: "\n").joined()
var error: Unmanaged<CFError>?
guard let privateKeyData = Data(base64Encoded: privateKey, options: .ignoreUnknownCharacters) else {
return nil
}
let attributes: [NSObject : NSObject] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits: NSNumber(value: 2048),
kSecReturnPersistentRef: true as NSObject
]
guard let secKey = SecKeyCreateWithData(privateKeyData as CFData, attributes as CFDictionary, &error) else {
return nil
}
let hash = createHash(string: string)
let algorithm: SecKeyAlgorithm = .rsaSignatureDigestPSSSHA256
guard let signature = SecKeyCreateSignature(secKey, algorithm, hash as CFData, &error) as Data? else {
throw error!.takeRetainedValue() as Error
}
return signature.base64EncodedString()
}
The commando you used to create the key encrypts the key. As far as I know, this is not supported by SecKeyCreateWithData
. You can remove the encryption by running the following command and entering your password.
openssl rsa -in WM_IO_private_key -out WM_IO_private_key
Remember, the private key is now unencrypted.
func signature(string: String) throws -> String? {
guard let url = Bundle.module.url(forResource: "WM_IO_private_key", withExtension: "") else {
return nil
}
let derString = try String(contentsOf: url)
.replacingOccurrences(of: "-----BEGIN RSA PRIVATE KEY-----", with: "")
.replacingOccurrences(of: "-----END RSA PRIVATE KEY-----", with: "")
.replacingOccurrences(of: "\n", with: "")
guard let derData = Data(base64Encoded: derString, options: .ignoreUnknownCharacters) else {
return nil
}
let attributes: [CFString: Any] = [
kSecClass: kSecClassKey,
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits: 2048
]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateWithData(derData as CFData, attributes as CFDictionary, &error) else {
throw error!.takeUnretainedValue()
}
// Instead of creating the hash yourself, let the security framework do it for you
// by using the rsaSignatureMessagePKCS1v15SHA256 algorithm. Alternatively you could
// use rsaSignatureMessagePSSSHA256, as PSS is a better algorithm.
// Make sure the key supports signing with the algorithm
guard SecKeyIsAlgorithmSupported(privateKey, .sign, .rsaSignatureMessagePKCS1v15SHA256) else {
return nil
}
guard let signature = SecKeyCreateSignature(privateKey, .rsaSignatureMessagePKCS1v15SHA256, Data(string.utf8) as CFData, &error) else {
throw error!.takeUnretainedValue()
}
return (signature as Data).base64EncodedString()
}