Issue decoding with RSA Public key

(Working on macOS and targetting macOS 10.11)


I'm having trouble getting my head around decoding a given 'secret message' using a given Public Key.

The 'secret message' is obtained from a device SDK call.

The public key is given to me by the device manufacturer.

The encyptedAPIToken is also gained from a SDK call to the device.


The idea is to decode the secret using the public key. This yields a value which is then used to decode another secret using SHA-256, the final result is used as a token in HTTP GET and PUT headers to the device.


The instructions from the device manufacturer are as shown in this picture.

Since I can't embed images in this post, incase you can't see that, it says :

1. Get the Public Key from the manufacturer.

2. Decrpyt secret message with public key using RSA algorithm.

3. Decrpyt encryptedAPIToken using SHA-256 algorithm using value gained in step 2.


So that all seems pretty reasonable.

Except I am having real trouble finding a way to decrpyt the secret message with the given public key.

All the Objective-C/Swift examples I can find are for iOS which uses SecKeyRef - which is not availble on macOS.


I found "Quin The Eskimo"'s CryptoCompatibility sample code from a few years back, but when I run it, like this:


./CryptoCompatibility rsa-small-decrypt public.pem sig

Where "public.pem" is my public key, and sig is the secret in a text file, I get this error:


CryptoCompatibility: error: NSOSStatusErrorDomain / -25256


I am struggling to find examples of how to decrpyt a message using an RSA Public key for Objective-C or Swift, on macOS.

Replies

Taking a look at CryptoCompatibility and the command that is being used, a few questions come to mind; first, when using the `rsa-small-decrypt` command it looks like this command uses the RSASmallDecryptCommand class and loads the private key and not the public key as described. Second, did you mean SHA-256 in this instance or possibly AES for step 3?


For more information on the private key being loaded, take a look at:


RSASmallDecryptCommand :

- (BOOL)runError:(NSError **)errorPtr


Matt Eaton

DTS Engineering, CoreOS

meaton3 at apple.com

Thanks @meaton


I've checked with the developer. They are sure they have given me the correct information. They say it is definitely a RSA Public key, and definitely SHA-256.


Anyway, I guess my original question still stands. How does one decrypt a given secret, using a given RSA Public key, using Objective-C or Swift on macOS? (Keep in mind, SecKeyRef is not available on macOS).

I’m confused. You’re asking how to decrypt data, but you have a public key. That’s not how RSA works. In RSA, one encrypts with the public key and decrypts with the private key.

The fact that you also mention SHA-256 suggestions that you’re actually trying to verify a signature (in RSA, you create a signature with a private key and verify it with the public key).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

ps DTS is closed 21 Dec through 1 Jan.

Aye, I know! You are right - it is in fact a signature I am verifying. I think it's the manufacturer who is using, shall we say, slightly wrong terminology..... beacuse I have checked and checked again with the device manufacturer and they are adam.... [nope wait, don't want to upset the "bad word" gremlins...] "very sure" that it is a "public key" and it is used to 'decrypt'.


However - as it happens - I have found the problem!

They key they gave me is too strong for USA policy. It fails with cryptic errors due to USA policy on key strengths.

I used Java test code with "unlimited key strength" policy files and it worked!

Java allows you to install policy files for jurisdictions with no laws against key strength. And it worked.


So... now... how to make this work in the USA where my App is destined....

If you replace public by private, that's how SSL works…


The idea is to decode the secret using the private key. This yields a value which is then used to decode another secret using SHA-256, the final result is used as a token in HTTP GET and PUT headers to the device.

So... now... how to make this work in the USA where my App is destined....


Will probably be hard. Strong encryption is submitted to very strict regulation.


Unless you or your manufacturer gets a special authorization (I don't know how to get it) from relevant US authority. Little hope.

Thanks everyone or all the input so far.

Here is a wee bit of code that I've put together in Java - which works!

Despite all the talk about RSA and not using PublicKey to decrypt - that is the terminology that the javax.crypto.* stuff uses.


Here it is:

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public String decrypt(String publicKeyStr, String secret, String encryptedToken) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException
{
        final Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, castStringToPublicKey(publicKeyStr));


        String password = new String(cipher.doFinal(Base64.getDecoder().decode(secret)));


        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] encodedHash = digest.digest(password.getBytes(StandardCharsets.UTF_8));


        final SecretKey secretKey = new SecretKeySpec(encodedHash, "AES");
        Cipher decoder = Cipher.getInstance("AES/ECB/PKCS5Padding");
        decoder.init(Cipher.DECRYPT_MODE, secretKey);


        return new String(decoder.doFinal(Base64.getDecoder().decode(encryptedToken)));


    }

private PublicKey castStringToPublicKey(String publicKeyStrPkcs8) throws InvalidKeySpecException, NoSuchAlgorithmException {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        String publicKeyStr = publicKeyStrPkcs8
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\\s+", "");


        byte[] publicKey = Base64.getDecoder().decode((publicKeyStr));
        EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKey);
        return keyFactory.generatePublic(publicKeySpec);
    }


Note lines 12 and 13 showing "decrypt" and "public" in the same sentence. I don't get it, but it works. It gives the exact correct output.

So, I guess the question is - can I do the equivalent in Objective-C or Swift?

If a doc is you encrypted with a private key, it is decrypted with the public key.


This is probably what you see here:

- you get a doc encrypted with private key of the sender (probably a signed hash of the document)

- you use the sender public key to decrypt and check it matches,

- hence authenticating the document.


So, terminology seems correct and consistent with what was said in previous messages.

If a doc is you encrypted with a private key, it is decrypted with the public key.

No, I’m sorry, but that’s just wrong. If the operation is encrypt, you must do it with the public key. If you’re using the private key to scramble plaintext, that operation is called sign.

While certain cyphers, like RSA, will work if you reverse things, you should never do that because it’s very easy to create security vulnerabilities that way. I’m not a crypto expert and thus I’m not going to try to explain this subtlety here.

What I can say definitively is that Apple’s crypto APIs enforce this distinction. Specifically, the only way to ‘decrypt’ with a private key is part of a verify operation, and the APIs that implement that operation only give you access to the verification result, a Boolean, and not the underlying bytes.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Eskimo

Thanks again - your insight into this is great - I wish I understood a fraction of what you do.

Now, I totally agree with you, and what you say tallies with everything I read on the RSA website, and other places.


So you can see how I am struggling to understand why the Java code gets away with things like...


final Cipher cipher = Cipher.getInstance("RSA");  
        cipher.init(Cipher.DECRYPT_MODE, castStringToPublicKey(publicKeyStr));  


        String password = new String(cipher.doFinal(Base64.getDecoder().decode(secret)));


...which returns the actual bytes, not just a boolean. And these are standard Java libraries too. They should know better!

Oh my head hurts!


I tried looking into the actual sources of javax.crypto.x but they are so abstract it is hard to see what is happening.


Thanks again! I truly appreciate your help.

Kenny.


.ps.Sorry for the late reply - I have subscribed to this thread, but I never get any notifications about replies.