Can't Repro
Status Update
Comments
ar...@google.com <ar...@google.com> #2
This issue does not reproduce with dev preview 4.
[Deleted User] <[Deleted User]> #3
Closing this issue as per comment #2 from reporter.
bo...@gmail.com <bo...@gmail.com> #4
We've seen this as well, we switched to aes keystore storage and hope the update gets out to users before Android O hits on monday.
[Deleted User] <[Deleted User]> #5
Is there any recommended changes to my code to support this or should the update make the existing code work? Thanks.
bo...@gmail.com <bo...@gmail.com> #6
I hope this issue is fixed in the actual Android-O release, but we were afraid to take that risk, so we proactively switch to AES storage. The RSA decrypting still works on Android-O, just a new encrypt-decrypt cycle doesn't, so we can do a conversion on Android M, N or O.
https://github.com/wiglenet/wigle-wifi-wardriving/blob/master/wiglewifiwardriving/src/main/java/net/wigle/wigleandroid/TokenAccess.java#L335
bo...@gmail.com <bo...@gmail.com> #7
We used cipher "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" which I believe is the default, and is mentioned in issue #36708951 which looks like the same problem. So you might be able to use one of the alternative cipher strings mentioned in that issue as well.
[Deleted User] <[Deleted User]> #8
Thanks. Using the suggestion for RSA/ECB/OAEPWithSHA-1AndMGF1Padding in
issue #36708951 seems to work just fine.
On Fri, Aug 18, 2017 at 12:16 PM, <buganizer-system@google.com> wrote:
issue #36708951 seems to work just fine.
On Fri, Aug 18, 2017 at 12:16 PM, <buganizer-system@google.com> wrote:
--
*Christopher Disdero *| Architect - Principal Engineer +1 360 348 8254 |
cdisdero@tibco.com <ihernand@tibco.com> |
<
*TIBCO Software* | 1700 Westlake Av. N. #500, Seattle, WA 98109
ar...@google.com <ar...@google.com> #9
Closing this issue as per comment #8 from reporter.
Please open a new issue if issue is observed again.
Please open a new issue if issue is observed again.
bo...@gmail.com <bo...@gmail.com> #10
Unfortunately this is still an issue in the android 8 released today, using the documented example KeyGenParameterSpec. This is a backwards incompatible breaking change which causes errors in existing apk's in the wild.
* "Example: RSA key pair for encryption/decryption using RSA OAEP":
https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html
* Reproduction code
7.1.2 output: "decrypted: moo"
8.0.0 output: "exception: javax.crypto.IllegalBlockSizeException"
try {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) return;
final String LOOKUP_KEY = "asdf";
final String ANDROID_KEYSTORE = "AndroidKeyStore";
final KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
if (keyStore.containsAlias(LOOKUP_KEY)) keyStore.deleteEntry(LOOKUP_KEY);
final KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE);
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
LOOKUP_KEY,
KeyProperties.PURPOSE_DECRYPT)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.build();
kpg.initialize(spec);
final KeyPair keyPair = kpg.generateKeyPair();
final String cipher = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
final Cipher encrypt = Cipher.getInstance(cipher);
encrypt.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
final Cipher decrypt = Cipher.getInstance(cipher);
decrypt.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
final byte[] encrypted = encrypt.doFinal("moo".getBytes());
final byte[] done = decrypt.doFinal(encrypted);
Log.i("token", "decrypted: " + new String(done));
} catch(final Exception ex) {
Log.e("token", "exception: " + ex, ex);
}
* "Example: RSA key pair for encryption/decryption using RSA OAEP":
* Reproduction code
7.1.2 output: "decrypted: moo"
8.0.0 output: "exception: javax.crypto.IllegalBlockSizeException"
try {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) return;
final String LOOKUP_KEY = "asdf";
final String ANDROID_KEYSTORE = "AndroidKeyStore";
final KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
if (keyStore.containsAlias(LOOKUP_KEY)) keyStore.deleteEntry(LOOKUP_KEY);
final KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE);
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
LOOKUP_KEY,
KeyProperties.PURPOSE_DECRYPT)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.build();
kpg.initialize(spec);
final KeyPair keyPair = kpg.generateKeyPair();
final String cipher = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
final Cipher encrypt = Cipher.getInstance(cipher);
encrypt.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
final Cipher decrypt = Cipher.getInstance(cipher);
decrypt.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
final byte[] encrypted = encrypt.doFinal("moo".getBytes());
final byte[] done = decrypt.doFinal(encrypted);
Log.i("token", "decrypted: " + new String(done));
} catch(final Exception ex) {
Log.e("token", "exception: " + ex, ex);
}
Description
Complete logged stack trace:
08-14 10:27:27.815 4455-4455/com.tibco.spotfire E/com.tibco.spotfireframework.services.SFKeystore: javax.crypto.IllegalBlockSizeException
java.io.IOException: javax.crypto.IllegalBlockSizeException
at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:133)
at javax.crypto.CipherInputStream.read(CipherInputStream.java:202)
at com.tibco.spotfireframework.services.SFKeystore.decryptString(SFKeystore.java:616)
at com.tibco.spotfireframework.services.SFKeystore.valueForKey(SFKeystore.java:165)
at com.tibco.spotfireframework.models.SFCredentials.retrieveCredentialsForURL(SFCredentials.java:123)
at com.tibco.spotfireframework.SFCollectionViewActivity.connectToServerItem(SFCollectionViewActivity.java:1055)
at com.tibco.spotfireframework.SFLibraryFolderViewActivity.populateCollectionView(SFLibraryFolderViewActivity.java:399)
at com.tibco.spotfireframework.SFLibraryFolderViewActivity.onStart(SFLibraryFolderViewActivity.java:212)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1333)
at android.app.Activity.performStart(Activity.java:6992)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2780)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Caused by: javax.crypto.IllegalBlockSizeException
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:519)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:531)
at javax.crypto.Cipher.doFinal(Cipher.java:1683)
at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:130)
at javax.crypto.CipherInputStream.read(CipherInputStream.java:202)
at com.tibco.spotfireframework.services.SFKeystore.decryptString(SFKeystore.java:616)
at com.tibco.spotfireframework.services.SFKeystore.valueForKey(SFKeystore.java:165)
at com.tibco.spotfireframework.models.SFCredentials.retrieveCredentialsForURL(SFCredentials.java:123)
at com.tibco.spotfireframework.SFCollectionViewActivity.connectToServerItem(SFCollectionViewActivity.java:1055)
at com.tibco.spotfireframework.SFLibraryFolderViewActivity.populateCollectionView(SFLibraryFolderViewActivity.java:399)
at com.tibco.spotfireframework.SFLibraryFolderViewActivity.onStart(SFLibraryFolderViewActivity.java:212)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1333)
at android.app.Activity.performStart(Activity.java:6992)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2780)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Caused by: android.security.KeyStoreException: Unknown error
at android.security.KeyStore.getKeyStoreException(KeyStore.java:695)
at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:224)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:506)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:531)
at javax.crypto.Cipher.doFinal(Cipher.java:1683)
at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:130)
at javax.crypto.CipherInputStream.read(CipherInputStream.java:202)
at com.tibco.spotfireframework.services.SFKeystore.decryptString(SFKeystore.java:616)
at com.tibco.spotfireframework.services.SFKeystore.valueForKey(SFKeystore.java:165)
at com.tibco.spotfireframework.models.SFCredentials.retrieveCredentialsForURL(SFCredentials.java:123)
at com.tibco.spotfireframework.SFCollectionViewActivity.connectToServerItem(SFCollectionViewActivity.java:1055)
at com.tibco.spotfireframework.SFLibraryFolderViewActivity.populateCollectionView(SFLibraryFolderViewActivity.java:399)
at com.tibco.spotfireframework.SFLibraryFolderViewActivity.onStart(SFLibraryFolderViewActivity.java:212)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1333)
at android.app.Activity.performStart(Activity.java:6992)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2780)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
The following sample code can be used to reproduce the issue:
/**
* The maximum validity time period for a keystore certificate.
*/
private static final int CERTIFICATE_MAX_YEARS = 100; /* This limit will never be reached */
/**
* Local reference to the Android keystore.
*/
private KeyStore keyStore = null;
...
private void initializeKeyStore() {
final String alias = "com.example.MyAndroidApp";
// Try to get the Android keystore provider.
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
} catch (KeyStoreException e) {
// Log exception
} catch (NoSuchAlgorithmException e) {
// Log exception
} catch (CertificateException e) {
// Log exception
} catch (IOException e) {
// Log exception
}
if (keyStore != null) {
boolean createKey = true;
try {
// If key exists...
if (keyStore.containsAlias(alias)) {
createKey = false;
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
try {
// Check that the key is still valid.
certificate.checkValidity();
} catch (CertificateExpiredException e) {
// Key is invalid, so delete it.
createKey = true;
keyStore.deleteEntry(alias);
} catch (CertificateNotYetValidException e) {
// Key is invalid, so delete it.
createKey = true;
keyStore.deleteEntry(alias);
}
}
if (createKey) {
// Make the key good for CERTIFICATE_MAX_YEARS from today.
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, CERTIFICATE_MAX_YEARS);
// Generate an RSA key pair in the Android KeyStore under the specified alias with a private key authorized to be used only for decryption using RSA OAEP encryption padding scheme with SHA-256 or SHA-512 digests. The public key is used for encryption/decryption.
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setKeyValidityStart(start.getTime())
.setKeyValidityEnd(end.getTime())
.build();
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
generator.initialize(spec);
// Generate the keypair and put in the Android keystore.
generator.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
// Log exception
} catch (NoSuchProviderException e) {
// Log exception
} catch (InvalidAlgorithmParameterException e) {
// Log exception
}
}
} catch (KeyStoreException e) {
// Log exception
}
}
}
private String encryptString(String clearText) {
// Bail on invalid string passed
if (clearText == null || clearText.length() == 0) {
return null;
}
// Initialize the keystore if necessary.
if (keyStore == null) {
initializeKeyStore();
}
// Get the public key used for encryption.
PublicKey publicKey = null;
try {
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
publicKey = privateKeyEntry.getCertificate().getPublicKey();
} catch (KeyStoreException e) {
// Log exception
} catch (NoSuchAlgorithmException e) {
// Log exception
} catch (UnrecoverableEntryException e) {
// Log exception
}
// Create a cipher for the encryption using the transformation appropriate for this platform.
Cipher input = null;
try {
input = Cipher.getInstance(transformationName);
input.init(Cipher.ENCRYPT_MODE, publicKey);
} catch (NoSuchAlgorithmException e) {
// Log exception
} catch (NoSuchPaddingException e) {
// Log exception
} catch (InvalidKeyException e) {
// Log exception
}
// Bail out on invalid input cipher.
if (input == null) {
return null;
}
// Encrypt the specified string.
byte [] encryptedBytes = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, input);
cipherOutputStream.write(clearText.getBytes("UTF-8"));
cipherOutputStream.close();
encryptedBytes = outputStream.toByteArray();
} catch (UnsupportedEncodingException e) {
// Log exception
} catch (IOException e) {
// Log exception
}
// Bail out on invalid encryption bytes
if (encryptedBytes == null) {
return null;
}
// Return the base64 encoded encrypted string.
return Base64.encodeToString(encryptedBytes, Base64.DEFAULT);
}
private String decryptString(String encrypted) {
// Bail on invalid string passed
if (encrypted == null || encrypted.length() == 0) {
return null;
}
// Initialize the keystore if necessary.
if (keyStore == null) {
initializeKeyStore();
}
// Get the private key used for decrypting
PrivateKey privateKey = null;
try {
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
privateKey = privateKeyEntry.getPrivateKey();
} catch (KeyStoreException e) {
// Log exception
} catch (NoSuchAlgorithmException e) {
// Log exception
} catch (UnrecoverableEntryException e) {
// Log exception
}
// Get the output cipher for decryption.
Cipher output = null;
try {
output = Cipher.getInstance(transformationName);
output.init(Cipher.DECRYPT_MODE, privateKey);
} catch (NoSuchAlgorithmException e) {
// Log exception
} catch (NoSuchPaddingException e) {
// Log exception
} catch (InvalidKeyException e) {
// Log exception
}
// Bail on invalid output cipher.
if (output == null) {
return null;
}
// Decrypt the encrypted string.
byte[] bytes = null;
try {
CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(Base64.decode(encrypted, Base64.DEFAULT)), output);
ArrayList<Byte> values = new ArrayList<>();
int nextByte;
/**
* EXCEPTION - java.io.IOException: javax.crypto.IllegalBlockSizeException - THROWN HERE ON FIRST CALL TO cipherInputStream.read()
**/
while ((nextByte = cipherInputStream.read()) != -1) {
values.add((byte) nextByte);
}
cipherInputStream.close();
bytes = new byte[values.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = values.get(i).byteValue();
}
} catch (IOException e) {
/** EXCEPTION - java.io.IOException: javax.crypto.IllegalBlockSizeException - CAUGHT HERE **/
}
// Get the clear text string from the decrypted bytes
String clearText = null;
if (bytes != null) {
try {
clearText = new String(bytes, 0, bytes.length, "UTF-8");
} catch (UnsupportedEncodingException e) {
// Log exception
}
}
return clearText;
}
...
// No exceptions and encryption seems to work.
String encryptedString = encryptString("This is the string to encrypt");
// Generates exception (java.io.IOException: javax.crypto.IllegalBlockSizeException) and decryption fails.
String decryptedString = decryptString(encryptedString);
// EXPECTED: decryptedString == "This is the string to encrypt" and no exceptions.