Digital Signatures


Asymmetric cryptosystems are not reduced to encryption only: another important trait of asymmetric cryptography is the availability of means to uniquely identify users. A data structure generated with the help of a user's private key can bind the user's identity to the information he created or sent. Such data structure is formed as a digital signature of an electronic document. The signature ensures both the integrity of the document and the authenticity of the signer. Digital signatures can have an impact on the legal status of the signer by enforcing non-repudiation: a user who signed a document with his private key cannot deny it and must bear the legal consequences of the act. Validity of the signature is verified by virtue of the corresponding public key:

The list of widely implemented signature algorithms includes the following:

  • DSA - Digital Signature Algorithm; DSA was adopted as one of Federal Information Processing Standards; DSA presumes the use of a hash function to create a condensed version of the data to be signed; the obtained message digest along with the signer's private key is fed to the DSA to generate the signature; the original message and the signature can be then transferred to any recipient possessing the signer's public key;
  • ECDSA - Elliptic Curve Digital Signature Algorithm; ECDSA is based on the algebraic properties of elliptic curves;
  • Russian GOST R 34.10-2012 (valid since January 1, 2013); the standard obsoletes GOST R 34.10-2001 which was, in turn, a more modern variation of GOST R 34.10-94; BouncyCastle software packages support the algorithm in its early versions;
  • ElGamal and qNEW algorithms available in some software libraries, e.g., in PyCrypto;
  • RSA signature scheme - part of the RSA asymmetric cryptosystem; we'll give a brief summary of the RSA signing techniques to formulate the basic notions of signature schemes.
RSA Signature Scheme

Signature and verification are considered cryptographic primitives. A signature primitive produces a signature s represented as an integer from a message m under the control of a private key K: Verification restores the primary message using the public key (n, e):

Primitives form the basis for more complex cryptographic operations. An RSA signature scheme with appendix consists of a signature generation operation and a signature verification operation. The recommended signature scheme is RSASSA-PSS: it combines signature/verification primitives with the EMSA-PSS encoding method. PSS stands for 'probabilistic signature scheme'. The formula of signature generation can be represented as

S=RSASSA-PSS-SIGN (K, M)

The process of signature creation includes the following steps:

  • the EMSA-PSS encoding method is applied to the message M; the output is the encoded message EM;
  • EM is converted to an integer message representative m; the conversion is based on the OS2IP primitive;
  • the signature primitive is applied to the message representative and the signer's private key; the result is an integer signature representative s;
  • the I2OSP data conversion primitive is applied to s; the final data structure is a signature S.

The EMSA-PSS encoding requires the use of a hash function to reduce the original message to a more compact representation. Other techniques necessary to obtain EM include getting a random value and applying a mask generation function (e.g., MGF1).

Signature verification formula is

RSASSA-PSS-VERIFY ((n, e), M, S)

Verification can be described as a sequence of the following stages:

  • the signature S is converted into an integer signature representative s by means of the OS2IP data conversion primitive;
  • the recipient's public key corresponding to the signer's private key is used to produce an integer message representative m from s;
  • m is transformed into EM;
  • the EMSA-PSS verification operation is performed on the message M whose signature is to be verified and the encoded message EM to determine if they are consistent; the same hash function and mask generation function which were used for signing are applied during verification.
Digital Signatures and OpenSSL

Generating an RSA key pair can be done by launching genrsa or genpkey commands:

openssl genrsa -out rsa.pem 1024

DSA and EC keys generation should be preceded by the creation of cryptographic parameters:

openssl genpkey -out dsaparams.pem -genparam -algorithm DSA
openssl gendsa -out dsa.pem params.pem

openssl ecparam -out ecparams.pem -name prime192v1
openssl genpkey -out ec.pem -paramfile ecparams.pem

The rsa, dsa ec or pkey commands enable keys manipulation:

openssl ec -inform PEM -outform DER -in ec.pem -out ec.der
openssl asn1parse -inform DER -in ec.der

Such commands as rsautl and pkeyutl are employed to sign or verify files:

openssl rsautl -in message.txt -out rsa.sig -inkey rsa.pem -sign
openssl rsautl -in rsa.sig -out original.txt -inkey rsa.pem -verify

The public key can be extracted from the file containg RSA keys, transferred to the signer's counteragents and used for signature verification:

openssl pkey -in rsa.pem -out public.pem -pubout
openssl rsautl -in rsa.sig -out original.txt -inkey public.pem -pubin -verify

If the signature kept in the rsa.sig file is valid, then message.txt and original.txt files must be identical. it can be checked via cmp command or an equivalent command line utility:

cmp message.txt restored.txt

Digital Signatures in PHP, Python, Java and C#

Creation of digital signatures in PHP is the responsibility of a set of functions belonging to the OpenSSL extension:

<?php
 $private_key_file=fopen("rsa.pem", "r");
 $public_key_file=fopen("public.pem", "r");
 $private_key_data=fread($private_key_file, filesize("rsa.pem"));
 $public_key_data=fread($public_key_file, filesize("public.pem"));
 fclose($private_key_file); fclose($public_key_file);
 $private_key=openssl_get_privatekey($private_key_data);
 $public_key=openssl_get_publickey($public_key_data);
 $status=openssl_sign("message", $ds, $private_key); // returns TRUE on success
 $verified=openssl_verify("message", $ds, $public_key);
 openssl_free_key($private_key); openssl_free_key($public_key);
?>

If the signature is valid, then $verified will return 1. If verification fails, the returned value is 0. -1 signals an error.

PyCrypto supports RSA, DSA, ElGamal and qNEW algorithms:

from Crypto.PublicKey import RSA
rsa=RSA.generate(1024)
ds=rsa.sign("message", "")
verification=rsa.verify("message", ds) # returns True

Standard Java cryptography supports RSA, DSA and ECDSA algorithms for signature creation. The algorithm is denoted in combination with a hash function to be applied to an original message, e. g., SHA1withDSA or SHA512withRSA. To sign a message in Java, the following steps should be taken. First, a cryptographic key pair is generated:

KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance("RSA");
KeyPair keyPair=keyPairGenerator.genKeyPair();
RSAPrivateKey privateKey=(RSAPrivateKey) keyPair.getPrivate();
RSAPublicKey publicKey=(RSAPublicKey) keyPair.getPublic();

Then an instance of java.security.Signature is created:

Signature signature=Signature.getInstance("SHA1withRSA"); // object is in the UNINITIALIZED state
signature.initSign(privateKey); // Signature object is in the SIGN state
signature.update("message".getBytes()); // data to sign is fed to RSA
byte[] ds=signature.sign(); // signature bytes

For demonstration purposes, the code below is resetting the same Signature object - this time for verification:

signature.initVerify(publicKey); // object is in the VERIFY state
signature.update("message".getBytes());
boolean verification=signature.verify(ds); // returns true

In more complex scenarios, the signature as well as the public key can be saved to external files and transferred to a recipient:

FileOutputStream dsStream=new FileOutputStream("ds");
dsStream.write(ds);
dsStream.close();
FileOutputStream publicKeyStream=new FileOutputStream("public_key.der");
publicKeyStream.write(publicKey.getEncoded());
publicKeyStream.close();

The public key file can be further analyzed with the help of an ASN.1 parser or converted into the PEM format:

openssl asn1parse -inform DER -in public_key.der
openssl rsa -inform DER -in public_key.der -outform PEM -out public_key.pem -pubin

A verifier will have to use the raw signature bytes and DER-encoded public key to check the validity of the signature:

FileInputStream dsInput=new FileInputStream("ds");
byte[] sig=new byte[dsInput.available()]; // raw signature bytes
dsInput.read(sig);
dsInput.close();

FileInputStream pkInput=new FileInputStream("public_key.der");
byte[] pk=new byte[pkInput.available()]; // encoded public key
pkInput.read(pk);
pkInput.close();

X509EncodedKeySpec pkSpec=new X509EncodedKeySpec(pk); // transparent specification of the public key material
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); // we need factory to convert data
RSAPublicKey publicKey=(RSAPublicKey)keyFactory.generatePublic(pkSpec); // from transparent to opaque representation
Signature signature=Signature.getInstance("SHA1withRSA"); // the same algorithm as applied for signing
signature.initVerify(publicKey); // Signature object is in the VERIFY state
signature.update("message".getBytes()); // loading message data
boolean verification=signature.verify(sig); // if signature is valid, the method returns true

.NET Framework developers employ classes derived from AsymmetricSignatureFormatter (responsible for creating digital signatures) and AsymmetricSignatureDeformatter (used for verification). Both RSA and DSA algorithms are available:

SHA1 sha1=SHA1.Create();
byte[] hash=sha1.ComputeHash(Encoding.Default.GetBytes("message"));
RSACryptoServiceProvider csp=new RSACryptoServiceProvider(1024);
RSAPKCS1SignatureFormatter formatter=new RSAPKCS1SignatureFormatter();
formatter.SetKey(csp);
formatter.SetHashAlgorithm("SHA1");
byte[] ds=formatter.CreateSignature(hash); // signature bytes
RSAPKCS1SignatureDeformatter deformatter=new RSAPKCS1SignatureDeformatter();
deformatter.SetKey(csp);
deformatter.SetHashAlgorithm("SHA1");
bool verification=deformatter.VerifySignature(hash, ds); // returns True

Signatures can also be created with the help of the SecurityDescription class:

SignatureDescription description=new SignatureDescription();
description.DigestAlgorithm="System.Security.Cryptography.SHA1CryptoServiceProvider";
description.KeyAlgorithm="System.Security.Cryptography.DSACryptoServiceProvider";
description.FormatterAlgorithm="System.Security.Cryptography.DSASignatureFormatter";
description.DeformatterAlgorithm="System.Security.Cryptography.DSASignatureDeformatter";
DSACryptoServiceProvider csp=new DSACryptoServiceProvider();
AsymmetricSignatureFormatter formatter=description.CreateFormatter(csp);
AsymmetricSignatureDeformatter deformatter=description.CreateDeformatter(csp);
. . .

JavaScript

The most convenient way to work with signatures in JavaScript is to use jsrsasign: the library exposes APIs enabling the developer to generate RSA and ECDSA keys as well as to handle ASN.1-encoded data structures:

// signing
var rsa=new RSAKey(); // Tom Wu's RSA Key instance
rsa.generate(1024, "10001"); // modulus is 1024 bits, public exponent is equal to 65537
var signature=new KJUR.crypto.Signature({"alg": "SHA1withRSA", "prov": "cryptojs/jsrsa"});
signature.initSign(rsa);
signature.updateString("message");
var ds=signature.sign(); // returns the signature as a hexadecimal string
// verification
var verification=new KJUR.crypto.Signature({"alg": "SHA1withRSA", "prov": "cryptojs/jsrsa"});
verification.initVerifyByPublicKey(rsa);
verification.updateString("message");
var verified=verification.verify(ds);
console.log(verified); // returns true