Java – IllegalArgumentException during Android KeyStore key generation

IllegalArgumentException during Android KeyStore key generation… here is a solution to the problem.

IllegalArgumentException during Android KeyStore key generation

Using AndroidKeyStore During the generation of the RSA key, I ran into the following issue in my application, not understanding that it can be easily copied from the Android SDK in the Basic AndroidKeyStore sample application. So if you have Locale.getDefault() == Locale.US, this example works fine, but if you change the locale to, for example, "ar_EG", it crashes abnormally:

java.lang.IllegalArgumentException: invalid date string: Unparseable
date: “af`cadaaedcaGMT+00:00” (at offset 0)
at
com.android.org.bouncycastle.asn1.DERUTCTime. (DERUTCTime.java:98)
at com.android.org.bouncycastle.asn1.x509.Time. (Time.java:62)
at
com.android.org.bouncycastle.x509.X509V3CertificateGenerator.setNotBefore(X509V3CertificateGenerator.java:112)
at
android.security.AndroidKeyPairGenerator.generateKeyPair(AndroidKeyPairGenerator.java:127)
at
java.security.KeyPairGenerator$KeyPairGeneratorImpl.generateKeyPair(KeyPairGenerator.java:276)
at
com.example.android.basicandroidkeystore.BasicAndroidKeyStoreFragment.createKeys(BasicAndroidKeyStoreFragment.java:237)

So the problem is the key validity time converted to string, which is done for the default locale.
This is from The code fragment class for ASN1UTCTime, which is used internally in KeyPairGenerator.generateKeyPair(), the method call is as follows:

public ASN1UTCTime(
    String time)
{
    this.time = Strings.toByteArray(time);
    try
    {
        this.getDate();
    }
    catch (ParseException e)
    {
        throw new IllegalArgumentException("invalid date string: " + e.getMessage());
    }
}

Before calling this method, the date object is passed to the following Time Constructor, which uses the default system locale:

public Time(
        Date    time)
    {
        SimpleTimeZone      tz = new SimpleTimeZone(0, "Z");
        SimpleDateFormat    dateF = new SimpleDateFormat("yyyyMMddHHmmss");
        dateF.setTimeZone(tz);
        String  d = dateF.format(time) + "Z";
        int     year = Integer.parseInt(d.substring(0, 4));
        if (year < 1950 || year > 2049)
        {
            this.time = new DERGeneralizedTime(d);
        }
        else
        {
            this.time = new DERUTCTime(d.substring(2));
        }
    } 

It’s weird because ASN1UTCTime The class has another constructor that seems better suited for international work :

/**
     * Base constructor from a java.util.date and Locale - you may need to use this if the default locale
     * doesn't use a Gregorian calender so that the GeneralizedTime produced is compatible with other ASN.1 implementations.
     *
     * @param time a date object representing the time of interest.
     * @param locale an appropriate Locale for producing an ASN.1 UTCTime value.
     */
    public ASN1UTCTime(
        Date time,
        Locale locale)
    {
        SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmss'Z'", locale);
        dateF.setTimeZone(new SimpleTimeZone(0,"Z"));
        this.time = Strings.toByteArray(dateF.format(time));
    } 

So, what is the right fix or how to suggest how to fix it?

Solution

This is a known issue with AndroidKeyStore

Android KeyStore does not get the locale correctly, which causes the device locale to malfunction from right to left. Example stack trace:

Caused by: java.lang.IllegalArgumentException: invalid date string: Unparseable date: "aga``eaeeb`eGMT+00:00" (at offset 0)
    at com.android.org.bouncycastle.asn1.DERUTCTime.<init>(DERUTCTime.java:98)
    at com.android.org.bouncycastle.asn1.x509.Time.<init>(Time.java:62)
    at com.android.org.bouncycastle.x509.X509V3CertificateGenerator.setNotBefore(X509V3CertificateGenerator.java:112)
    at android.security.AndroidKeyPairGenerator.generateKeyPair(AndroidKeyPairGenerator.java:128)
    at java.security.KeyPairGenerator$KeyPairGeneratorImpl.generateKeyPair(KeyPairGenerator.java:275)

The workaround is to set the English locale before generating the key pair, and then change it back to:

/**
 * Generates RSA keys.
 */
private void generateRsaKeys(Context context, String rsaAlias) {
    try {
         Set English locale as default (workaround)
        Locale initialLocale = Locale.getDefault();
        setLocale(Locale.ENGLISH);
         Generate the RSA key pairs
        Calendar start = Calendar.getInstance();
        Calendar end = Calendar.getInstance();
        end.add(Calendar.YEAR, 30);  30 years
        KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
                .setAlias(rsaAlias)
                .setSubject(new X500Principal("CN=" + rsaAlias + ", O=Organization"))
                .setSerialNumber(BigInteger.TEN)
                .setStartDate(start.getTime())
                .setEndDate(end.getTime())
                .build();
        KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA, ANDROID_KEY_STORE);
        kpg.initialize(spec);
        kpg.generateKeyPair();
         Reset default locale
        setLocale(initialLocale);
    } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
        Log.e(e, "generateRsaKeys: ");
    }
}

/**
 * Sets default locale.
 */
private void setLocale(Locale locale) {
    Locale.setDefault(locale);
    Resources resources = context.getResources();
    Configuration config = resources.getConfiguration();
    config.locale = locale;
    resources.updateConfiguration(config, resources.getDisplayMetrics());
}

Related Problems and Solutions