package org.nick.androidkeystore; import java.io.IOException; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableEntryException; import java.security.cert.CertificateException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import org.spongycastle.crypto.AsymmetricBlockCipher; import org.spongycastle.crypto.CipherParameters; import org.spongycastle.crypto.InvalidCipherTextException; import android.util.Log; public class AndroidRsaEngine implements AsymmetricBlockCipher { private static final String TAG = AndroidRsaEngine.class.getSimpleName(); private static final boolean DEBUG = false; private String keyAlias; private boolean isSigner; private Cipher cipher; private KeyStore keyStore; private RSAPrivateKey privateKey; private RSAPublicKey publicKey; private boolean forEncryption; private CipherParameters params; public AndroidRsaEngine(String keyAlias, boolean isSigner) { this.keyAlias = keyAlias; this.isSigner = isSigner; try { this.cipher = Cipher.getInstance("RSA/ECB/NoPadding"); this.keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); java.security.KeyStore.Entry keyEntry = keyStore.getEntry( this.keyAlias, null); publicKey = (RSAPublicKey) ((java.security.KeyStore.PrivateKeyEntry) keyEntry) .getCertificate().getPublicKey(); privateKey = (RSAPrivateKey) ((java.security.KeyStore.PrivateKeyEntry) keyEntry) .getPrivateKey(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (NoSuchPaddingException e) { throw new RuntimeException(e); } catch (KeyStoreException e) { throw new RuntimeException(e); } catch (CertificateException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } catch (UnrecoverableEntryException e) { throw new RuntimeException(e); } } @Override public int getInputBlockSize() { int bitSize = publicKey.getModulus().bitLength(); if (forEncryption) { return (bitSize + 7) / 8 - 1; } else { return (bitSize + 7) / 8; } } @Override public int getOutputBlockSize() { int bitSize = publicKey.getModulus().bitLength(); if (forEncryption) { return (bitSize + 7) / 8; } else { return (bitSize + 7) / 8 - 1; } } @Override public void init(boolean forEncryption, CipherParameters param) { this.forEncryption = forEncryption; if (DEBUG) { Log.d(TAG, "forEncryption: " + forEncryption); } this.params = param; if (DEBUG) { Log.d(TAG, "CipherParameters: " + param); } try { if (forEncryption) { cipher.init(Cipher.ENCRYPT_MODE, isSigner ? privateKey : publicKey); } else { cipher.init(Cipher.DECRYPT_MODE, isSigner ? publicKey : privateKey); } } catch (InvalidKeyException e) { throw new RuntimeException(e); } } @Override public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException { try { String inputStr = Crypto.toHex(in); if (DEBUG) { Log.d(TAG, "processBlock() INPUT: " + inputStr); } byte[] result = cipher.doFinal(in, inOff, inLen); String outputStr = Crypto.toHex(result); if (DEBUG) { Log.d(TAG, "processBlock() OUTPUT: " + outputStr); } byte[] converted = convertOutput(result); String convertedStr = Crypto.toHex(converted); if (DEBUG) { Log.d(TAG, "processBlock() CONVERTED: " + convertedStr); } return converted; } catch (IllegalBlockSizeException e) { throw new InvalidCipherTextException("Illegal block size: " + e.getMessage()); } catch (BadPaddingException e) { throw new InvalidCipherTextException("Bad padding: " + e.getMessage()); } } // from BC's RSACoreEngine public byte[] convertOutput(byte[] output) { if (forEncryption) { if (output[0] == 0 && output.length > getOutputBlockSize()) // have ended up with an extra zero byte, copy down. { byte[] tmp = new byte[output.length - 1]; System.arraycopy(output, 1, tmp, 0, tmp.length); return tmp; } if (output.length < getOutputBlockSize()) // have ended up with less bytes than normal, lengthen { byte[] tmp = new byte[getOutputBlockSize()]; System.arraycopy(output, 0, tmp, tmp.length - output.length, output.length); return tmp; } } else { if (output[0] == 0) // have ended up with an extra zero byte, copy down. { byte[] tmp = new byte[output.length - 1]; System.arraycopy(output, 1, tmp, 0, tmp.length); return tmp; } } return output; } }