Introduction
The rsa digital signature java tutorial covers the RSASSA-PSS and RSASSA-PKCS1-v1_5 digital signature algorithms, which are the two signature schemes standardized for RSA in RFC 8017 (PKCS 1 version 2.2). The rsa digital signature java example below demonstrates both schemes with tamper detection. RSA signatures were part of the original RSA paper by Rivest, Shamir, and Adleman in 1978; the PKCS1 v1.5 signature scheme was standardized in 1993 and the PSS scheme in 1999. The rsa digital signature java implementation here shows the JDK Signature class with the "SHA256withRSA" and "SHA256withRSA/PSS" algorithm names. PSS is the modern recommendation for new designs because it has a security proof under the random oracle model; PKCS1 v1.5 remains in wide deployment because of backward compatibility.
For a Java learner the rsa signature java example below matters because the JDK ships the Signature class with the "SHA256withRSA" and "SHA256withRSA/PSS" algorithms, which are the two production ways to sign and verify with RSA in Java. The tutorial shows both algorithms and demonstrates the signature-then-verify round trip with tamper detection.
The TLS 1.3 handshake uses RSA-PSS for the signature in the CertificateVerify message (the server proves it owns the certificate's private key by signing a transcript of the handshake with RSA-PSS). JWT signing with RS256 (RSASSA-PKCS1-v1_5 with SHA-256) is a common deployment pattern in REST APIs; the modern recommendation is to use PS256 (RSASSA-PSS with SHA-256) for new designs.
What is RSA Digital Signature?
rsa signature java is an asymmetric cryptographic signature scheme. The signer holds the private key (n, d) and produces a signature sigma = m to the power d modulo n, where m is the message representative (the encoded hash of the message). The verifier holds the public key (n, e) and checks whether sigma to the power e modulo n equals m. The signature proves that the signer held the private key and that the message was not modified in transit.
The signature scheme belongs to the family of asymmetric signature primitives, in which the signing key is private and the verification key is public. Other primitives in the family are DSA (1991), ECDSA (1998), and EdDSA (2011). RSA signatures are unique in this family for their simplicity: the signing and verification are both modular exponentiations, and the key generation is shared with RSA encryption.
The two standardized RSASSA (RSA Signature Scheme with Appendix) variants differ in the encoding of the hash. RSASSA-PKCS1-v1_5 uses a deterministic encoding (the hash is prefixed with a DER-encoded algorithm identifier and padded with the PKCS1 v1.5 type-1 padding). RSASSA-PSS uses a probabilistic encoding (the hash is masked with a random salt and a MGF1-derived mask, and the salt is recovered as part of the signature verification). PSS has a security proof; PKCS1 v1.5 does not, but no practical attack on PKCS1 v1.5 is known.
How RSA Digital Signature Works Step by Step
Step 1: Hash the message. The signer computes the hash H of the message M using a cryptographic hash function such as SHA-256. The hash reduces the message to a fixed-size digest that fits in the RSA modulus.
Step 2: Encode the hash. RSASSA-PKCS1-v1_5 encodes H as 0x00 || 0x01 || PS || 0x00 || T, where PS is a string of 0xFF bytes of length (modulus length - hash length - 3) and T is the DER-encoded algorithm identifier for SHA-256 concatenated with H. RSASSA-PSS encodes H as the result of a more complex padding scheme that uses a random salt and the MGF1 mask generation function.
Step 3: Sign. The signer computes the signature sigma = EM to the power d modulo n, where EM is the encoded message representative. The signature is the same length as the modulus (256 bytes for a 2048-bit key).
Step 4: Transmit the message and signature. The signer sends (M, sigma) to the verifier. The signature is an appendix to the message, not an encryption of the message.
Step 5: Hash the received message. The verifier computes the hash H of M. If the message was modified in transit, H will not match the encoded hash in the signature.
Step 6: Verify. The verifier computes EM' = sigma to the power e modulo n. For RSASSA-PKCS1-v1_5, the verifier checks EM' is byte-identical to the expected encoded message. For RSASSA-PSS, the verifier checks EM' passes the PSS verification, which recovers the salt and re-derives the hash.
Step 7: Accept or reject. If the verification succeeds, the verifier accepts the message as authentic. If the verification fails, the verifier rejects the message and the signature.
Java Program for RSA Digital Signature Verification
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SecureRandom;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import java.util.Base64;
public class RsaSignatureDemo {
public static void main(String[] args) throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048, new SecureRandom());
KeyPair keys = kpg.generateKeyPair();
byte[] message = "Transfer $5000 to account 1234-5678".getBytes("UTF-8");
// === RSASSA-PKCS1-v1_5 with SHA-256 (algorithm name "SHA256withRSA") ===
Signature pkcs1 = Signature.getInstance("SHA256withRSA");
pkcs1.initSign(keys.getPrivate());
pkcs1.update(message);
byte[] sigPkcs1 = pkcs1.sign();
pkcs1.initVerify(keys.getPublic());
pkcs1.update(message);
boolean okPkcs1 = pkcs1.verify(sigPkcs1);
// Tamper with one byte and verify again.
byte[] tampered = message.clone();
tampered[5] ^= 0x01;
pkcs1.initVerify(keys.getPublic());
pkcs1.update(tampered);
boolean tamperDetectedPkcs1 = !pkcs1.verify(sigPkcs1);
// === RSASSA-PSS with SHA-256 (algorithm name "SHA256withRSA/PSS") ===
Signature pss = Signature.getInstance("SHA256withRSA/PSS");
pss.setParameter(new PSSParameterSpec(
"SHA-256", // hash algorithm
"MGF1", // mask generation function
MGF1ParameterSpec.SHA256,
32, // salt length (in bytes)
1 // trailer field (BC's RSA-PSS trailer)
));
pss.initSign(keys.getPrivate());
pss.update(message);
byte[] sigPss = pss.sign();
pss.initVerify(keys.getPublic());
pss.update(message);
boolean okPss = pss.verify(sigPss);
// Two PSS signatures of the same message differ (probabilistic).
pss.initSign(keys.getPrivate());
pss.update(message);
byte[] sigPss2 = pss.sign();
boolean pssDiffers = !java.util.Arrays.equals(sigPss, sigPss2);
System.out.println("Key bit length : " + keys.getPrivate().getAlgorithm());
System.out.println("Message : " + new String(message, "UTF-8"));
System.out.println();
System.out.println("=== RSASSA-PKCS1-v1_5 ===");
System.out.println("Signature (b64) : " + Base64.getEncoder().encodeToString(sigPkcs1).substring(0, 40) + "...");
System.out.println("Signature length : " + sigPkcs1.length + " bytes");
System.out.println("Verify ok : " + okPkcs1);
System.out.println("Tamper detected : " + tamperDetectedPkcs1);
System.out.println();
System.out.println("=== RSASSA-PSS ===");
System.out.println("Signature (b64) : " + Base64.getEncoder().encodeToString(sigPss).substring(0, 40) + "...");
System.out.println("Signature length : " + sigPss.length + " bytes");
System.out.println("Verify ok : " + okPss);
System.out.println("Two sigs differ : " + pssDiffers);
}
}
Code Explanation Line by Line
The class RsaSignatureDemo uses the JDK KeyPairGenerator to produce a 2048-bit RSA key pair. The main method signs and verifies the same message with two signature schemes: RSASSA-PKCS1-v1_5 (the algorithm name "SHA256withRSA") and RSASSA-PSS (the algorithm name "SHA256withRSA/PSS"). The message is a sample bank transfer instruction that is small enough to fit in a single RSA block.
The RSASSA-PKCS1-v1_5 signature uses the Signature class in the standard way: initSign initializes the signer with the private key, update feeds the message bytes, and sign produces the signature. The verifier uses initVerify with the public key, update with the message bytes, and verify with the signature. The signature is 256 bytes (the size of the 2048-bit modulus).
The tamper detection test flips one bit in the message and re-verifies. The signature was produced for the original message, so the verification of the tampered message should fail. The test confirms the signature scheme detects the tampering, which is the integrity guarantee of digital signatures.
The RSASSA-PSS signature uses the PSSParameterSpec to configure the signature with the SHA-256 hash, the MGF1 mask generation function with SHA-256, a 32-byte salt, and the trailer field 1 (the standard PSS trailer). The salt makes the signature probabilistic: two signatures of the same message differ, which is the property that gives PSS its security proof. The test confirms that two PSS signatures of the same message differ, which is the probabilistic property.
The Signature class is the production way to sign and verify with RSA in Java. The algorithm names "SHA256withRSA" and "SHA256withRSA/PSS" are standard in the SunRsaSign provider. The BouncyCastle provider accepts the same names and also supports "SHA3-256withRSA" for the SHA-3 family of hash functions.
Sample Input and Output
Key bit length : RSA Message : Transfer $5000 to account 1234-5678 === RSASSA-PKCS1-v1_5 === Signature (b64) : kL3...base64-prefix-here... Signature length : 256 bytes Verify ok : true Tamper detected : true === RSASSA-PSS === Signature (b64) : mN7...base64-prefix-here... Signature length : 256 bytes Verify ok : true Two sigs differ : true
Both signature schemes produce a 256-byte signature for a 2048-bit RSA key. The verification succeeds for the original message and fails for the tampered message, confirming the integrity guarantee. The two PSS signatures of the same message differ, confirming the probabilistic property.
Common Mistakes to Avoid
The first mistake is using the deprecated "SHA1withRSA" algorithm. SHA-1 has been broken for signatures (the SHAttered collision attack by Stevens et al. in 2017 produced a real-world SHA-1 collision), and the SHA-1 family of RSA signatures is no longer considered secure. Use "SHA256withRSA" or "SHA256withRSA/PSS" for new designs.
The second mistake is using RSASSA-PKCS1-v1_5 for new designs. PKCS1 v1.5 has no security proof, and although no practical attack is known, the cryptographic community prefers schemes with proofs. Use RSASSA-PSS for new designs; use PKCS1 v1.5 only for backward compatibility with existing systems.
The third mistake is using a 1024-bit RSA key. NIST SP 800-57 recommends 2048 bits minimum for new keys and 3072 bits for keys that need to remain secure after 2030. The 1024-bit RSA keys used in some legacy systems can be factored with significant resources but are within reach of nation-state adversaries.
The fourth mistake is verifying the signature before checking the certificate chain. In TLS, the certificate chain must be verified (each certificate is signed by its parent, and the root must be in the trust store) before the leaf certificate's signature is verified. Skipping the chain verification allows an attacker to present a self-signed certificate that the verifier accepts.
Real-World Applications
RSA signatures are the most widely deployed asymmetric signature primitive in the world. TLS 1.3 uses RSA-PSS for the signature in the CertificateVerify message: the server signs a transcript of the handshake with the leaf certificate's private key, and the client verifies the signature with the leaf certificate's public key. The certificate chain itself uses RSA-PKCS1-v1_5 with SHA-256 for backward compatibility (the chain is signed by certificate authorities that may have used PKCS1 v1.5).
JWT (JSON Web Token) signing with RS256 (RSASSA-PKCS1-v1_5 with SHA-256) is a common deployment pattern in REST APIs. The Spring Security OAuth2 library supports RS256, RS384, RS512 (PKCS1 v1.5) and PS256, PS384, PS512 (PSS). The modern recommendation is to use PS256 for new designs because of the security proof, although RS256 remains the default for backward compatibility.
SSH uses rsa-sha2-256 and rsa-sha2-512 for the RSA signature algorithms (which use SHA-2 instead of the obsolete SHA-1 of the original ssh-rsa algorithm). The OpenSSH client and server prefer the SHA-2 variants since OpenSSH 7.6 (2017) and disabled the original ssh-rsa algorithm in OpenSSH 8.2 (2020) for security reasons.
Pretty Good Privacy (PGP) and S/MIME email use RSA signatures for the message signing layer. The standard signature algorithms are RSA with SHA-256 (the modern recommendation) and RSA with SHA-1 (the legacy algorithm, still supported for backward compatibility but disabled by default in modern PGP clients).
The rsa signature java tutorial in this post shows both the legacy RSASSA-PKCS1-v1_5 and the modern RSASSA-PSS schemes. New designs should use RSASSA-PSS with a 2048-bit or 3072-bit key; the legacy scheme should be used only for backward compatibility. For new designs that need asymmetric signatures, ECDSA and EdDSA offer smaller keys and faster signing for equivalent security.
Conclusion
In this rsa digital signature java tutorial we built the RSASSA-PKCS1-v1_5 and RSASSA-PSS signature schemes using the JDK Signature class, demonstrated the signature-then-verify round trip with tamper detection, and explained why PSS is the modern recommendation for new designs. The rsa digital signature java example shows both the legacy RSASSA-PKCS1-v1_5 and the modern RSASSA-PSS schemes, with a 2048-bit RSA key. The rsa digital signature java implementation is the production pattern for JWT, TLS, and SSH signatures. RSA signatures are the most widely deployed asymmetric signature primitive; for new designs, use RSASSA-PSS with a 2048-bit or 3072-bit key. Next, study the RSA key generation tutorial, the DSA and ECDSA tutorials, and the EdDSA tutorial.
Comments
Comments
Post a Comment