Digital Signatures¶
Digital signatures use a key pair to provide authentication and integrity. The private key signs a message, producing a signature that anyone with the corresponding public key can verify. This proves both who produced the data and that it has not been tampered with.
Assumed imports
import dev.whyoleg.cryptography.*
import dev.whyoleg.cryptography.algorithms.*
val provider = CryptographyProvider.Default
Basic Usage¶
Generate a key pair, sign a message, and verify the signature using ECDSA with P-256:
val ecdsa = provider.get(ECDSA)
val keyPair = ecdsa.keyPairGenerator(EC.Curve.P256).generateKey()
// Sign a message with the private key
val message = "Transfer 100 EUR to Alice".encodeToByteArray()
val signature = keyPair.privateKey
.signatureGenerator(digest = SHA256, format = ECDSA.SignatureFormat.DER)
.generateSignature(message)
// Verify -- throws if the signature is invalid
keyPair.publicKey
.signatureVerifier(digest = SHA256, format = ECDSA.SignatureFormat.DER)
.verifySignature(message, signature)
There are two ways to verify the signature:
verifySignaturethrows an exception on failure.tryVerifySignaturereturnstrueorfalse.
val verifier = keyPair.publicKey
.signatureVerifier(digest = SHA256, format = ECDSA.SignatureFormat.DER)
// Option 1: throws on failure
verifier.verifySignature(message, signature)
// Option 2: Boolean result
val isValid = verifier.tryVerifySignature(message, signature)
Both signatureGenerator and signatureVerifier return reusable objects – create them once and
call generateSignature or verifySignature as many times as needed.
For larger data, the overload that accepts a RawSource from kotlinx-io could be used instead:
val source: RawSource = ... // file, network stream, etc.
val signature = keyPair.privateKey
.signatureGenerator(digest = SHA256, format = ECDSA.SignatureFormat.DER)
.generateSignature(source)
keyPair.publicKey
.signatureVerifier(digest = SHA256, format = ECDSA.SignatureFormat.DER)
.verifySignature(source, signature)
Pass-Through¶
Use updatingSource or updatingSink to compute a signature as data flows through
a kotlinx-io pipeline:
val signFunction = keyPair.privateKey
.signatureGenerator(SHA256, ECDSA.SignatureFormat.DER)
.createSignFunction()
// Wrap a source -- data passes through AND is fed into the signature
val signingSource: RawSource = signFunction.updatingSource(messageSource)
// Read all data (it goes to both the consumer and the signature function)
val data = signingSource.readByteArray()
// After reading, the signature is ready
val signature = signFunction.signToByteArray()
Both SignFunction and VerifyFunction implement AutoCloseable and support
reset for a reuse. After finalization, call close, or wrap it in a use block:
val signature = keyPair.privateKey
.signatureGenerator(SHA256, ECDSA.SignatureFormat.DER)
.createSignFunction().use { sf ->
// ... feed data to function
sf.signToByteArray()
}
For the most control, use update directly to feed data in arbitrary chunks:
val signFunction = keyPair.privateKey
.signatureGenerator(SHA256, ECDSA.SignatureFormat.DER)
.createSignFunction()
signFunction.update("first chunk ".encodeToByteArray())
signFunction.update("second chunk".encodeToByteArray())
val signature = signFunction.signToByteArray()
Incremental verification works the same way:
val verifyFunction = keyPair.publicKey
.signatureVerifier(SHA256, ECDSA.SignatureFormat.DER)
.createVerifyFunction()
verifyFunction.update("first chunk ".encodeToByteArray())
verifyFunction.update("second chunk".encodeToByteArray())
verifyFunction.verify(signature) // throws on failure
Supported Algorithms¶
| Algorithm | JDK | WebCrypto | Apple | CryptoKit | OpenSSL3 |
|---|---|---|---|---|---|
| RSA-PSS | |||||
| RSA-PKCS1 | |||||
| ECDSA |
|||||
| EdDSA |
|||||
| DSA |