How to sign on a javacard applet and return the signature to the host application - digital-signature

I have the following function in the javacard applet that is supposed to receive a challenge from the host application, sign it, and return it to the host via command-response apdu communication.
private void sign(APDU apdu) {
if(!pin.isValidated())ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
else{
byte[] buffer = apdu.getBuffer();
byte [] output = new byte [20];
short length = 20;
short x =0;
Signature signature =Signature.getInstance(Signature.ALG_RSA_SHA_PKCS1, false);
signature.init(privKey, Signature.MODE_SIGN);
short sigLength = signature.sign(buffer, offset,length, output, x);
//This sequence of three methods sends the data contained in
//'serial' with offset '0' and length 'serial.length'
//to the host application.
apdu.setOutgoing();
apdu.setOutgoingLength((short)output.length);
apdu.sendBytesLong(output,(short)0,(short)output.length);
}
}
The host computes the challenge as follows and sends it to the javacard applet for signing:
//produce challenge
SecureRandom random = SecureRandom . getInstance( "SHA1PRNG" ) ;
byte [ ]bytes = new byte [ 20 ] ;
random . nextBytes ( bytes) ;
CommandAPDU challenge;
ResponseAPDU resp3;
challenge = new CommandAPDU(IDENTITY_CARD_CLA,SIGN_CHALLENGE, 0x00, 0x00,bytes ,20 );
resp3= c.transmit(challenge);
if(resp3.getSW()==0x9000) {
card_signature = resp2.getData();
String s = new String(card_signature);
System.out.println("signature " + s);
}else System.out.println("Challenge signature error: " + resp3.getSW());
As you can see, I check for both succesful and unsuccesful signing but I get the following printed out:
Challenge signature error:28416
Where exactly do I go wrong? Is it possible I retrieve the challenge in a faulty way with `byte[] buffer = apdu.getBuffer(); or is my signature all wrong?

You are trying to sign using an RSA key. However, the signature size of an RSA generated signature is identical to the key size (the modulus size) encoded in a minimum number of bytes. So e.g. a 2048 bit key results in a signature with size ceil(2028D / 8D) = 256 bytes (the maximum response size, unless you use extended length APDU's).
You should never create byte arrays in Java except when creating the class or when personalizing the applet. Any array created in persistent memory using new byte[] will likely remain until the garbage collector is run, and it may wear out the EEPROM or flash. And for signatures you don't need persistent memory.
If you look at the Signature.sign method:
The input and output buffer data may overlap.
So you can just generate the signature into the APDU buffer instead. Otherwise you can generate it in a JCSystem.makeTransientByteArray created buffer, but if you want to communicate it to the client you'll have to copy it into the APDU buffer anyway.
Please don't ever do the following:
String s = new String(card_signature);
A signature is almost indistinguishable from random bytes, so printing this out will generate just garbage. If you need text output try hexadecimals or base 64 encoding of the signature. Or print it as decimal number (but note that this may lead to loss of leading bytes with value 00).

Related

Getting Exception CryptoException.ILLEGAL value when using HMACKey.setkey

I want to Generate HMAC_SHA1 Signature in JavaCard Applet
I am trying to sign a message which contains in inBuffer byte array S (byte array , 64 byte). The snippet of the function from javacard (jc) applet module is given below. I am using javacard3.0.1 library for developing jc applet.
Signature m_sessionMAC = null;
HMACKey keyType = null;
// Create HMAC Key Used in Mac
m_sessionMAC = Signature.getInstance(Signature.ALG_HMAC_SHA_1, false);
// Create HMAC Key Used in Mac
keyType = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, KeyBuilder.LENGTH_HMAC_SHA_256_BLOCK_64, false);
keyType.setKey(S,(short) 0, (short) S.length);
this keyType.setKey result in exception as ILLEGAL_VALUE, please guide me what am i doing wrong?
Key length is specified in bits -- citing KeyBuilder.buildKey() documentation:
keyLength - the key size in bits. The valid key bit lengths are key type dependent. Some common key lengths are listed above in the LENGTH_* constants, for example LENGTH_DES.
Which means:
use 512 for a 64 byte key
use 64 for a 8 byte key
Note that you can use any key length for HMAC-SHA1, but keys longer than block size (which is 64 bytes for SHA-1) are transformed into their SHA-1 hash before use (see e.g. here).
Good luck with your project!

Sending a byte [] over javacard apdu

I send a byte [] from the host application to the javacard applet. But when I try to retrieve it as byte [] via the command buffer[ISO7816.OFFSET_CDATA], I am told that I cannot convert byte to byte[]. How can I send a byte [] via command APDU from the host application and retrieve it as byte[] on the other end (javacard applet). It appears buffer[ISO7816.OFFSET_CDATA] returns byte. See my comments on where the error occurs.
My idea is as follows:
The host application sends challenge as a byte [] to be signed by the javacard applet. Note that the signature requires the challenge to be a byte []. The javacard signs as follows:
private void sign(APDU apdu) {
if(!pin.isValidated()) ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
else{
byte [] buffer = apdu.getBuffer();
byte numBytes = buffer[ISO7816.OFFSET_LC];
byte byteRead =(byte)(apdu.setIncomingAndReceive());
if ( ( numBytes != 20 ) || (byteRead != 20) )
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
byte [] challenge = buffer[ISO7816.OFFSET_CDATA];// error point cannot convert from byte to byte []
byte [] output = new byte [64];
short length = 64;
short x =0;
Signature signature =Signature.getInstance(Signature.ALG_RSA_SHA_PKCS1, false);
signature.init(privKey, Signature.MODE_SIGN);
short sigLength = signature.sign(challenge, offset,length, output, x); // challenge must be a byte []
//This sequence of three methods sends the data contained in
//'serial' with offset '0' and length 'serial.length'
//to the host application.
apdu.setOutgoing();
apdu.setOutgoingLength((short)output.length);
apdu.sendBytesLong(output,(short)0,(short)output.length);
}
}
The challenge is sent by the host application as shown below:
byte [] card_signature=null;
SecureRandom random = SecureRandom . getInstance( "SHA1PRNG" ) ;
byte [] bytes = new byte [ 20 ] ;
random . nextBytes ( bytes) ;
CommandAPDU challenge;
ResponseAPDU resp3;
challenge = new CommandAPDU(IDENTITY_CARD_CLA,SIGN_CHALLENGE, 0x00, 0x20,bytes);
resp3= c.transmit(challenge);
if(resp3.getSW()==0x9000) {
card_signature = resp3.getData();
String s= DatatypeConverter.printHexBinary(card_signature);
System.out.println("signature: " + s);
} else System.out.println("Challenge signature error " + resp3.getSW());
Generally, you send bytes over through the APDU interface. A Java or Java Card byte[] is a construct that can hold those bytes. This is where the APDU buffer comes in: it is the byte array that holds the bytes sent over the APDU interface - or at least a portion of them after calling setIncomingAndReceive().
The challenge therefore is within the APDU buffer; instead of calling:
short sigLength = signature.sign(challenge, offset,length, output, x);
you can therefore simply call:
short sigLength = signature.sign(buffer, apdu.getOffsetCdata(), CHALLENGE_SIZE, buffer, START);
where CHALLENGE_SIZE is 20 and START is simply zero.
Then you can use:
apdu.getOutgoingAndSend(START, sigLength);
to send back the signed challenge.
If you require to keep the challenge for a later stage then you should create a byte array in RAM using JCSystem.makeTransientByteArray() during construction of the Applet and then use Util.arrayCopy() to move the byte values into the challenge buffer. However, since the challenge is generated by the offcard system, there doesn't seem to be any need for this. The offcard system should keep the challenge, not the card.
You should not use ISO7816.OFFSET_CDATA anymore; it will not return the correct result if you would use larger key sizes that generate larger signatures and therefore require the use of extended length APDUs.

Converting ElGamal encryption from encrypting numbers to strings

I've have the following ElGamal encryption scheme
const forge = require('node-forge');
const bigInt = require("big-integer");
// Generates private and public keys
function keyPairGeneration(p, q, g) {
var secretKey = bigInt.randBetween(2, q.minus(2));
var publicKey = g.modPow(secretKey, p);
const keys = {
secret: secretKey,
public: publicKey
}
return keys;
}
// Generates a proxy and a user key
function generateProxyKeys(secretKey) {
const firstKey = bigInt.randBetween(1, secretKey);
const secondKey = secretKey.minus(firstKey);
const keys = {
firstKey: firstKey,
secondKey: secondKey
}
return keys;
}
// Re-encrypts
function preEncrypt(p, q, g, m, publicKey) {
const k = bigInt.randBetween(1, q.minus(1));
const c1 = g.modPow(k, p);
// g^x = publicKey
// m.publicKey^k
const c2 = bigInt(m).multiply(publicKey.modPow(k, p)).mod(p);
const c = {
c1: c1,
c2: c2
}
return c;
}
function preDecrypt(p, c1, c2, key) {
// (mg^xr) / (g^rx1)
var decrypt = c2.multiply(c1.modPow(key, p).modInv(p)).mod(p);
return decrypt;
}
Which works fine with numbers. However, I want to be able to use it to encrypt strings (btw, it's not a regular ElGamal, I don't think the difference is that relevant in this context but for more details see this question I asked)
I thought about converting the string to an integer, running the encryption, and converting back to a string whenever I needed it. I couldn't find a way of doing this in JS (there was this question posted here but the code didn't work). There is another similar question but it's in Java and the method mentioned there is not provided by the BigInt implementation in JS.
Is there any easy way of converting a string to a BigInt?
Arbitrarily long messages
Asymmetric encryption should not be used to encrypt messages of arbitrary length, because it is much slower than symmetric encryption. So, we can use symmetric encryption for the actual message and asymmetric encryption for the key that encrypted the message.
There are basically two ways for arbitrary sized messages:
If prime p is big enough that it fits a common key size of a symmetric cipher such as AES, then you can simply generate a random AES key (128, 192 or 256 bit) and use an AES-derived scheme such as AES-GCM to encrypt your message. Afterwards, you decode a number from the AES key (use fromArray) to be used as m in your ElGamal-like encryption scheme. This is called hybrid encryption.
Regardless how big prime p is, you can always generate a random m number in the range of 1 to p-1 and use that to produce your asymmetric ciphertext. Afterwards, you can take the previously generated m, encode it into a byte array (use toString(16) to produce a Hex-encoded string and then simply parse it as Hex for the hashing) and hash it with a cryptographic hash function such as SHA-256 to get your AES key. Then you can use the AES key to encrypt the message with a symmetric scheme like AES-GCM. This is called key encapsulation.
The main remaining thing that you have to look out for is data format: How do you serialize the data for the asymmetric part and the symmetric part of the ciphertext? How do you read them back that you can always tell them apart? There are many possible solutions there.
Short messages
If the messages that you want to encrypt have a maximum size that is smaller than the prime that you use, then you don't need the two approaches above. You just need to take the byte representation of the message and convert it to a big integer. Something like this:
var arr = Array.prototype.slice.call(Buffer.from("some message"), 0);
var message = bigInt.fromArray(arr, 256);
This is a big endian encoding.
This makes only sense if your prime is big enough which it should be for security.

Using output of MD5 of the key to decode stream using Base64 in java

for one of my tasks i am supposed to create MD5 hash of the key provided and use the hash output of MD5 as a key to Base64 decoding.
My question is:
1. Is it possible to decode a string in Base64 using a private key?
So far this is what i have done.
String key = MD5.getMD5("K3b2mTr3g0s1_B-m");//MD5.getMD5(key) will return MD5 hash of key passed
byte[] raw = key.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
if (inputString != null) {
byte[] encrypted = cipher.doFinal(inputString.getBytes());
BASE64Encoder encoder = new BASE64Encoder();
encryptedString = encoder.encode(encrypted);
System.out.print("encrypted string :" + encryptedString);
}
For the above implementation i am getting an exception: Invalid key foundjava.security.InvalidKeyException: Illegal key size or default parameters
The output of MD5 consists of 16 bytes.
An AES key consists of 16, 24 or 32 bytes.
Thus it is technically possible to use the output of MD5 as AES key.
Your problem is probably that the length of byte[] raw is not 16.
Indeed, MD5 operates on bytes, but in your implementation, the input and output are Strings. Also, the output of getBytes() is not well-defined (platform dependent).
Thus I suggest:
verify the length of bytes[] raw,
adapt your code for getMD5 to have bytes[] as input and output,
do not use getBytes(), convert with an explicit encoding instead.

Generate HMAC_SHA256 Signature in JavaCard Applet

I am trying to sign a message which contains in inBuffer byte array using my own derived key S (also byte array). The snippet of the function from javacard (jc) applet module is given below. I am using javacard2.2.2 library for developing jc applet. I am using android application for sending process request. I am reciving return code '6A81' which means 'function not supported'. Now, I have no clue that how to proceed as I failed to understand that it is mentioning about HMAC_SHA256 not supported or I am making some mistake in the function. Please help.
Signature m_sessionMAC = null;
HMACKey keyType = null;
Sign = new byte[64];
bytesRead = apdu.setIncomingAndReceive();
// Create HMAC Key Used in Mac
m_sessionMAC = Signature.getInstance(Signature.ALG_HMAC_SHA_256, false);
// Create HMAC Key Used in Mac
keyType = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, KeyBuilder.LENGTH_HMAC_SHA_256_BLOCK_64, false);
keyType.setKey(S,(short) 0, (short) S.length);
m_sessionMAC.init(keyType, Signature.MODE_SIGN);
//Generate Signature on inBuffer (received data to sign)
echoOffset = m_sessionMAC.sign(inBuffer, ISO7816.OFFSET_CDATA, ISO7816.OFFSET_LC, Sign , (short)0);
Util.arrayCopyNonAtomic(Sign, ( short ) 0, inBuffer, ( short ) 0, echoOffset);
apdu.setOutgoingAndSend( ( short ) 0, (short) echoOffset );
Please help me in this regards or also provide any pointers for implementing HMAC_SHA256 or HMAC_SHA1 symmetric crypto. in javacard applet.
Thank you in advance.
Most cryptographic algorithms are optional for a JavaCard. Therefore it may be that your card does not support Signature.ALG_HMAC_SHA_256. But HMAC algorithm isn't very complex therefore you should check if your card supports MessageDigest.ALG_SHA_256.
If it is supported you can follow RFC2104 and implement HMAC yourself:
K = HMAC key of length 32
ipad = the byte 0x36 repeated 32 times
opad = the byte 0x5C repeated 32 times.
To compute HMAC over the data `text' we perform
H(K XOR opad, H(K XOR ipad, text))
You can test your implementation by comparing your result with the test vectors noted in RFC 4231
In addition to Robert answer I'd like to highlight that you have to check CryptoException when you are calling getInstance() method. As Robert already mentioned algorithms could be optional hence it is good practice to check before like:
try {
Signature.getInstance(Signature.ALG_HMAC_SHA_256, false);
} catch (CryptoException e) {
if (e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
// Do something to treat algorithm absebce
}
}

Resources