OperationKey usedUse case
EncryptRecipient's public keySecure key exchange, small payloads
DecryptRecipient's private keyRecover encrypted data
SignSigner's private keyProve authenticity and integrity
VerifySigner's public keyConfirm signature is genuine
RSA is slow for large data. In practice, use hybrid encryption: encrypt the actual data with AES-GCM, then encrypt the AES key with RSA. TLS does exactly this.
import java.security.*; import java.util.Base64; // Generate a 2048-bit RSA key pair (use 4096 for highest security) KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(2048, new SecureRandom()); KeyPair keyPair = kpg.generateKeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); // Serialise to Base64-encoded DER format String pubBase64 = Base64.getEncoder().encodeToString(publicKey.getEncoded()); String privBase64 = Base64.getEncoder().encodeToString(privateKey.getEncoded()); System.out.println("Public : " + pubBase64.substring(0, 40) + "..."); System.out.println("Private : " + privBase64.substring(0, 40) + "...");

Always use OAEP (Optimal Asymmetric Encryption Padding) — the older PKCS1v1.5 padding is vulnerable to Bleichenbacher's attack.

import javax.crypto.*; import javax.crypto.spec.OAEPParameterSpec; import javax.crypto.spec.PSource; import java.security.*; import java.security.spec.*; import java.util.Base64; public class RsaEncryption { private static final String TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; public static byte[] encrypt(byte[] plaintext, PublicKey publicKey) throws Exception { Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.ENCRYPT_MODE, publicKey); return cipher.doFinal(plaintext); } public static byte[] decrypt(byte[] ciphertext, PrivateKey privateKey) throws Exception { Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(ciphertext); } public static void main(String[] args) throws Exception { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(2048); KeyPair kp = kpg.generateKeyPair(); byte[] message = "Top secret!".getBytes(StandardCharsets.UTF_8); byte[] encrypted = encrypt(message, kp.getPublic()); byte[] decrypted = decrypt(encrypted, kp.getPrivate()); System.out.println(new String(decrypted)); // Top secret! } } RSA-2048 with OAEP-SHA256 can encrypt at most 190 bytes of plaintext (2048 bits − OAEP overhead). For larger data, encrypt the data with AES-GCM and encrypt only the AES key with RSA.

A digital signature proves that a message was created by the holder of the private key and has not been modified. The Signature class handles signing and verification.

import java.security.*; import java.util.Base64; public class RsaSignature { private static final String ALGORITHM = "SHA256withRSA"; public static byte[] sign(byte[] data, PrivateKey privateKey) throws Exception { Signature sig = Signature.getInstance(ALGORITHM); sig.initSign(privateKey); sig.update(data); return sig.sign(); } public static boolean verify(byte[] data, byte[] signature, PublicKey publicKey) throws Exception { Signature sig = Signature.getInstance(ALGORITHM); sig.initVerify(publicKey); sig.update(data); return sig.verify(signature); } public static void main(String[] args) throws Exception { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(2048); KeyPair kp = kpg.generateKeyPair(); byte[] document = "Sign this contract.".getBytes(StandardCharsets.UTF_8); byte[] signature = sign(document, kp.getPrivate()); System.out.println("Signature: " + Base64.getEncoder().encodeToString(signature)); System.out.println("Valid : " + verify(document, signature, kp.getPublic())); // true // Tamper with document document[0] = 'X'; System.out.println("Tampered : " + verify(document, signature, kp.getPublic())); // false } }
import java.security.*; import java.security.spec.*; import java.util.Base64; // Serialise keys to PEM-style Base64 String publicPem = "-----BEGIN PUBLIC KEY-----\n" + Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(publicKey.getEncoded()) + "\n-----END PUBLIC KEY-----"; String privatePem = "-----BEGIN PRIVATE KEY-----\n" + Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(privateKey.getEncoded()) + "\n-----END PRIVATE KEY-----"; // Deserialise KeyFactory kf = KeyFactory.getInstance("RSA"); // Public key (X.509 SubjectPublicKeyInfo format) byte[] pubBytes = Base64.getMimeDecoder().decode( publicPem.replace("-----BEGIN PUBLIC KEY-----", "") .replace("-----END PUBLIC KEY-----", "").trim()); PublicKey restoredPub = kf.generatePublic(new X509EncodedKeySpec(pubBytes)); // Private key (PKCS#8 format) byte[] privBytes = Base64.getMimeDecoder().decode( privatePem.replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "").trim()); PrivateKey restoredPriv = kf.generatePrivate(new PKCS8EncodedKeySpec(privBytes));

The standard pattern for encrypting arbitrary-length data with RSA:

StepOperationKey
1Generate a random 256-bit AES session key
2Encrypt the plaintext with AES-GCM using the session keyAES session key
3Encrypt the AES session key with RSA-OAEPRecipient's RSA public key
4Send: encrypted AES key + IV + AES ciphertext
5Recipient: decrypt AES key with RSA private keyRecipient's RSA private key
6Recipient: decrypt data with AES session keyDecrypted AES key
// Sender side KeyGenerator aesKg = KeyGenerator.getInstance("AES"); aesKg.init(256); SecretKey sessionKey = aesKg.generateKey(); // Encrypt data with AES-GCM byte[] encryptedData = aesGcmEncrypt(data, sessionKey); // Encrypt session key with RSA-OAEP Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); rsaCipher.init(Cipher.ENCRYPT_MODE, recipientPublicKey); byte[] encryptedKey = rsaCipher.doFinal(sessionKey.getEncoded()); // Transmit: encryptedKey + encryptedData // Recipient side rsaCipher.init(Cipher.DECRYPT_MODE, recipientPrivateKey); byte[] keyBytes = rsaCipher.doFinal(encryptedKey); SecretKey recoveredKey = new SecretKeySpec(keyBytes, "AES"); byte[] plaintext = aesGcmDecrypt(encryptedData, recoveredKey);