A small, fully-offline Android app that decrypts a password-protected PEM private key and gives you back both:
- a ready-to-use
java.security.PrivateKeyobject, and - the key re-serialized as an unencrypted PKCS#8 PEM (
-----BEGIN PRIVATE KEY-----).
Everything happens on-device — the app requests no INTERNET permission.
| Input | Header |
|---|---|
| PKCS#8 encrypted (PBES2 / AES, etc.) | -----BEGIN ENCRYPTED PRIVATE KEY----- |
| Traditional OpenSSL PKCS#1 (RSA/EC) | -----BEGIN RSA PRIVATE KEY----- + Proc-Type / DEK-Info |
OpenSSH (openssh-key-v1, bcrypt) |
-----BEGIN OPENSSH PRIVATE KEY----- |
| Already-unencrypted keys | BEGIN PRIVATE KEY / BEGIN RSA PRIVATE KEY / BEGIN OPENSSH … (passed through) |
RSA, EC/ECDSA, and Ed25519 keys are covered. The PKCS#8 / PKCS#1 paths are backed by
BouncyCastle (bcprov + bcpkix); OpenSSH keys are
handled by SSHJ.
app/src/main/java/com/example/pemdecryptor/
├── crypto/PemKeyDecryptor.kt # reusable, Android-free crypto core
├── MainActivity.kt # Jetpack Compose UI
├── DecryptViewModel.kt # UI state; runs decryption off the main thread
└── ui/theme/Theme.kt # Material 3 theme (dynamic color on Android 12+)
app/src/test/java/.../PemKeyDecryptorTest.kt # pure-JVM unit tests
If you only want the logic, drop crypto/PemKeyDecryptor.kt into your own project
(it has no Android dependencies, only BouncyCastle) and call:
val decrypted = PemKeyDecryptor.decrypt(pemText, password.toCharArray())
decrypted.privateKey // java.security.PrivateKey
decrypted.unencryptedPem // unencrypted PKCS#8 PEM String
decrypted.algorithm // "RSA", "EC", …It throws WrongPasswordException for a bad passphrase and UnsupportedKeyException
for input that isn't a parseable private key.
Android provider note: the PKCS#8 / PKCS#1 paths create their own
BouncyCastleProvider()instance and pass it explicitly to every builder. For OpenSSH keys, SSHJ resolves EC keys via the JCE provider registered as"BC"; since Android ships a stripped-down"BC", the core replaces it once with the full BouncyCastle implementation (seeensureFullBouncyCastle). RSA and Ed25519 OpenSSH keys work even without that step.
Requires the Android SDK (via Android Studio, or ANDROID_HOME + command-line
tools). Open the project in Android Studio (Ladybug or newer) and Run, or:
# Unit tests for the crypto core — no device/emulator needed:
./gradlew :app:testDebugUnitTest
# Build the debug APK:
./gradlew :app:assembleDebugToolchain used: AGP 8.7.3, Kotlin 2.0.21, Compose BOM 2024.10.01,
minSdk 24, targetSdk 35, JDK 17.
# Generate an encrypted test key:
openssl genrsa -aes256 -out enc.pem 2048 # asks for a passphrase
# In the app: Load .pem file → enter the passphrase → Decrypt.
# Copy or Save the result, then verify it:
openssl rsa -in exported.pem -check -noout- No network permission; nothing is uploaded.
- The passphrase is handled as a
CharArrayand zero-filled after use. - Key material is never written to logs.
- A decrypted, unencrypted key is sensitive — treat any exported PEM accordingly.