Berry Blue
Berry Blue

Reputation: 16482

How to create a signature from an RSA private key in Swift

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()
}

Upvotes: 0

Views: 1218

Answers (2)

Bram
Bram

Reputation: 3264

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()
}

Upvotes: 1

user19910190
user19910190

Reputation: 1

As of today, auth signature code is available in Java (https://www.walmart.io/docs/affiliate/onboarding-guide) The idea we provided sample code to help the customers to implement the logic at customer end by referring it. You can implement the logic in(C# .NET, Python, PHP or JS) in such a way that whenever your system invoking Walmart APIs, generate the signature on the fly and pass as input parameter. This is how all of customers implemented and consuming our APIs.

Please refer the below documentation for complete. https://walmart.io/docs/affiliate/quick-start-guide https://www.walmart.io/docs/affiliate/onboarding-guide

Regards, Firdos IO Support

Upvotes: 0

Related Questions