I have faced the following problem in my application during RSA key generation using AndroidKeyStore, than understood that it could be easily reproduced in the BasicAndroidKeyStore sample app from the Android SDK. So, if your have Locale.getDefault() == Locale.US
than this sample works well, but if you change locale to, for instance, "ar_EG"
, it would crash with exception:
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 in the key validity time converting to String which is made with respect to a default locale.
Here is code snippet from ASN1UTCTime class, which is used under the hood of KeyPairGenerator.generateKeyPair()
following method call:
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 to this method Date object is being passed to the following Time constructor, which uses 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));
}
}
This is very strange, because ASN1UTCTime class has another constructor, which is seems to be more suitable 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 correct fix or advise how to solve this problem?
This is a known issue with AndroidKeyStore.
Android KeyStore doesn't correctly take in the locale and it causes the failures for device locale with language from right to left. Sample 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)
A workaround is setting English locale before generating a key pair and changing it back after all:
/**
* 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());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With