Ωmega
Ωmega

Reputation: 43703

Keystore operation failed with RSA sign and verify

I have difficulties to use RSA for signing and verifying. Below is part of my RSA class that I use. The methods init(), privateKey() and publicKey() work fine with encrypt() and decrypt() methods (last two not listed here), so I assume I made some mistake inside of my sign() method. Method verify() wasn't tested yet, because signing was unsuccessful, so I wasn't able to move further. Please take a look...


My RSA class is:

class RSA
{
    private final static String ANDROID_KEY_STORE = "AndroidKeyStore";

    public final static int ANY_PURPOSE = KeyProperties.PURPOSE_ENCRYPT | 
            KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_SIGN | 
            KeyProperties.PURPOSE_VERIFY;

    public final static long CENTURY = (100 * 365) + 24;

    public enum KeySize
    {
        BIT_512  ( 512),
        BIT_768  ( 768),
        BIT_1024 (1024),
        BIT_2048 (2048),
        BIT_3072 (3072),
        BIT_4096 (4096);

        private int value;

        KeySize(int value)
        {
            this.value = value;
        }

        public int value()
        {
            return this.value;
        }
    }

    public enum SignatureAlgorithm
    {
        MD5WithRSA        ("MD5WithRSA"),
        SHA1WithRSA       ("SHA1WithRSA"),
        SHA1WithRSA_PSS   ("SHA1WithRSA/PSS"),
        SHA224WithRSA     ("SHA224WithRSA"),
        SHA224WithRSA_PSS ("SHA224WithRSA/PSS"),
        SHA256WithRSA     ("SHA256WithRSA"),
        SHA256WithRSA_PSS ("SHA256WithRSA/PSS"),
        SHA384WithRSA     ("SHA384WithRSA"),
        SHA384WithRSA_PSS ("SHA384WithRSA/PSS"),
        SHA512WithRSA     ("SHA512WithRSA"),
        SHA512WithRSA_PSS ("SHA512WithRSA/PSS");

        private String value;

        SignatureAlgorithm(String value)
        {
            this.value = value;
        }

        public String value()
        {
            return this.value;
        }
    }

    public static KeyPair init(final String alias, final RSA.KeySize keySize, 
                final long validityDays, final Boolean reset)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, 
            IOException, NoSuchProviderException, InvalidAlgorithmParameterException
    {
        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
        keyStore.load(null);
        if (reset || (!keyStore.containsAlias(alias)))
        {
            final long now = java.lang.System.currentTimeMillis();
            KeyPairGenerator keyPairGenerator = KeyPairGenerator
                    .getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE);
            keyPairGenerator.initialize(
                    new KeyGenParameterSpec.Builder(alias, RSA.ANY_PURPOSE)
                    .setRandomizedEncryptionRequired(false)
                    .setDigests(
                            KeyProperties.DIGEST_NONE,   KeyProperties.DIGEST_MD5,
                            KeyProperties.DIGEST_SHA1,   KeyProperties.DIGEST_SHA224,
                            KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384,
                            KeyProperties.DIGEST_SHA512)
                    .setKeySize(keySize.value())
                    .setEncryptionPaddings(
                            KeyProperties.ENCRYPTION_PADDING_NONE,
                            KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
                            KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                    .setCertificateSubject(new X500Principal(
                            "CN=Android, O=Android Authority"))
                    .setCertificateSerialNumber(new BigInteger(256, new Random()))
                    .setCertificateNotBefore(new Date (now - (now % 1000L)))
                    .setCertificateNotAfter(new Date(((new Date(now - (now % 1000L)))
                            .getTime()) + (validityDays * 86400000L)))
                    .build());
            return keyPairGenerator.generateKeyPair();
        }
        else return null;
    }

    public static PublicKey publicKey(final String alias)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, 
            IOException, UnrecoverableEntryException
    {
        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
        keyStore.load(null);
        return keyStore.getCertificate(alias).getPublicKey();
    }

    public static PrivateKey privateKey(final String alias)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, 
            IOException, UnrecoverableEntryException
    {
        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
        keyStore.load(null);
        return (PrivateKey) keyStore.getKey(alias, null);
    }

    public static byte[] sign(final String message, final String alias, 
                final SignatureAlgorithm algorithm)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, 
            IOException, UnrecoverableEntryException,
            InvalidKeyException, SignatureException
    {
        Signature instance = Signature.getInstance(algorithm.value());
        instance.initSign(privateKey(alias), new SecureRandom());
        instance.update(message.getBytes("UTF-8"));
        return instance.sign();
    }

    public static Boolean verify(final String message, final byte[] signature, 
                final String alias, final SignatureAlgorithm algorithm)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, 
            IOException, UnrecoverableEntryException,
            InvalidKeyException, SignatureException
    {
        Signature instance = Signature.getInstance(algorithm.value());
        instance.initVerify(publicKey(alias));
        instance.update(message.getBytes("UTF-8"));
        return instance.verify(signature);
    }
}

My Hex class is:

class Hex
{
    private final static char[] hexArray = "0123456789ABCDEF".toCharArray();

    public static String bytesToHexString(final byte[] bytes)
    {
        char[] hexChars = new char[2 * bytes.length];
        for (int i = 0; i < bytes.length; i++)
        {
            int b = bytes[i] & 0xFF;
            hexChars[2 * i] = hexArray[b >>> 4];
            hexChars[2 * i + 1] = hexArray[b & 0x0F];
        }
        return new String(hexChars);
    }
}

My test code is:

try
{
    RSA.init("RSA", RSA.KeySize.BIT_512, RSA.CENTURY, true);

    RSA.SignatureAlgorithm signatureAlgorithm = RSA.SignatureAlgorithm.SHA1WithRSA;

    byte[] x = RSA.sign("My message", "RSA", signatureAlgorithm);
    Log.d("RSA Test", "x: " + Hex.bytesToHexString(x));

    Boolean y = RSA.verify("My message", x, "RSA", signatureAlgorithm);
    Log.d("RSA Test", "y: " + (y? "True" : "False"));
}
catch (Exception e)
{
    Log.d("RSA Test", "ERROR: " + e.toString());
}

And the output is:

ERROR: java.security.InvalidKeyException: Keystore operation failed

Debugging shows that the error occur inside of my RSA.sign() method, when it calls initSign().

What is wrong in my code?

Upvotes: 1

Views: 3553

Answers (1)

President James K. Polk
President James K. Polk

Reputation: 42019

If you are signing with an RSA key you must set a signature padding mode in the KeyGenParameterSpec. You haven't done so. For example, in your init() method you might add the following line right after .setEncryptionPaddings():

.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)

e.g.

keyPairGenerator.initialize(
                    new KeyGenParameterSpec.Builder(alias, RSA.ANY_PURPOSE)
                            .setRandomizedEncryptionRequired(false)
                            .setDigests(
                                    KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_MD5,
                                    KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA224,
                                    KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384,
                                    KeyProperties.DIGEST_SHA512)
                            .setKeySize(keySize.value())
                            .setEncryptionPaddings(
                                    KeyProperties.ENCRYPTION_PADDING_NONE,
                                    KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
                                    KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                            .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
                            .setCertificateSubject(new X500Principal(
                                    "CN=Android, O=Android Authority"))
                            .setCertificateSerialNumber(new BigInteger(256, new Random()))
                            .setCertificateNotBefore(new Date(now - (now % 1000L)))
                            .setCertificateNotAfter(new Date(((new Date(now - (now % 1000L)))
                                    .getTime()) + (validityDays * 86400000L)))
                            .build());

The other supported signature padding mode is SIGNATURE_PADDING_RSA_PSS. Note that PSS signatures and OAEP encryption are the more modern versions of RSA block formatting for signing and encrypting. They provide better security guarantees and should always be preferred over the other modes unless they cannot be used because of interoperability issues with existing code bases.

The signature algorithm names that end in "PSS", e.g. SHA224WithRSA_PSS are obviously only for PSS padding, whereas the others are only for PKCS1 padding.

Upvotes: 10

Related Questions