Encrypt Functions

1.For our file encryption and subsequent decryption, we define the following two functions:

int encryptFile( char * sender, char * recipient, char *ifile,char * ofile );
int decryptFile( char * sender, char * recipient, char *ifile,char * ofile ); 

We want the encrypt function to take the public key of the receiving party (recipient), encrypt the data (ifile) with the given key and sign the encrypted data with the sender's private key (sender), before outputting and encoding the file to the output file (ofile).

For error handling purposes, we define the function as follows:

#undef FN 
#define FN "encryptFile:"
int encryptFile( char * sender, char * recipient,char * ifile, char * ofile )

2.We now need to define the required PKCS#11 data types pertaining to the session, slot identification, and object handles we will use for the sender and recipient keys.

/* sender slot key session handles */
CK_SLOT_ID hsSlot;
CK_OBJECT_HANDLE hsKey = 0;
CK_SESSION_HANDLE hsSession;
/* recipient slot key session handles */
CK_SLOT_ID hrSlot;
CK_OBJECT_HANDLE hrKey;
CK_SESSION_HANDLE hrSession;

3. We must also allocate variables to define the type of mechanism, digest, and key information during encryption.

CK_RV rv; /* Return Value for PKCS#11 function */
CK_MECHANISM mech; /* Structure for cipher mechanism
*/
CK_BYTE iv[8]; /* Init. Vector used with CBC
encryption */
CK_BYTE digest[80];
CK_SIZE len;
CK_OBJECT_HANDLE hKey; /* random encrypting key */
CK_BYTE wrappedKey[2 * 1024];
CK_SIZE wrappedKeyLen;
CK_BYTE signature[2 * 1024];
unsigned long fileSize;
unsigned long encodedSize;

Earlier, we said that we wanted to be able to perform password-based encryption via a runtime switch, so accordingly this is the first instance that we check for with our pflag variable.

4.Our next step is to define our secret key that we will use to encrypt the data.  The key type to be used is double-length DES.  The CK_BBOOL refers to a byte-sized Boolean flag that we have defined as either TRUE or FALSE for easier reference.

CK_ATTRIBUTE is a structure that includes the type, value, and length of an attribute.  Since every PKCS#11 key object is required to be assigned certain attributes, this structure is later used during our key derivation and generation to assign those attributes to the key.

if ( pflag ) {
/* use PBE to do the encryption */
static CK_OBJECT_CLASS at_class = CKO_SECRET_KEY;
static CK_KEY_TYPE kt = CKK_DES2;
static const CK_BBOOL True = TRUE;
static const CK_BBOOL False = FALSE;
CK_ATTRIBUTE attr[] = {
{CKA_CLASS, &at_class, sizeof(at_class)},{CKA_KEY_TYPE, &kt, sizeof(at_class)},{CKA_EXTRACTABLE, (void*)&True, sizeof(True)},{CKA_SENSITIVE, (void*)&False, sizeof(False)},{CKA_DERIVE, (void*)&True, sizeof(True)}};

5.The params variable is defined using the PKCS#11 definition CK_PBE_PARAMS, a structure that provides all of the necessary information required by the PKCS#11 password-based encryption mechanisms.

CK_BYTE iv[8];
CK_PBE_PARAMS params;
memset(&params, 0x0, sizeof(CK_PBE_PARAMS));
params.pInitVector = iv;
params.pPassword = sender;
params.passwordLen = strlen(sender);
params.pSalt = NULL;
params.saltLen = 0;
params.iteration = 1;

6.PKCS#11 also uses a structure for defining the mechanism.  Within CK_MECHANISM we need to specify the mechanism type, a pointer to the parameters we defined earlier and the size of the parameters.  The mechanism type we will use is CKM_PBE_SHA1_DES2_EDE_CBC that is used for generating a 2-key triple-DES secret key and IV from a password and a salt value by using the SHA-1 digest algorithm and an iteration count.

memset(&mech, 0x0, sizeof(CK_MECHANISM));
mech.mechanism = CKM_PBE_SHA1_DES2_EDE_CBC;
mech.pParameter = &params;
mech.parameterLen = sizeof(CK_PBE_PARAMS);

7.We have now set up our required structures, and the next step is to open a session between the application and a token in a particular slot using the PKCS#11 call C_OpenSession. This call requires the slot ID flags which indicate the type of session, an application-defined pointer to be passed to the notification callback; an address of the notification callback function, and a pointer to the location that receives the handle for the new session.

rv = C_OpenSession(0, CKF_RW_SESSION|CKF_SERIAL_SESSION,NULL,NULL, &hsSession);
if ( rv ) return 1;
hrSession = hsSession;

8.Once we have successfully opened a session with the token, we now want to generate the key that we will use to encrypt our input file.  The C_GenerateKey function will generate a secret key and thereby create a new key object.  This function call requires the session’s handle, a pointer to the key generation mechanism, a pointer to the template for the new key, the number of attributes in the template and a pointer to the location that receives the handle of the new key.

The CHECK_RV() function call is part of the SafeNet ProtectToolkit-C extended capability for better error feedback and handling.

rv = C_GenerateKey(hsSession, &mech, attr, NUMITEMS(attr),&hKey); 
CHECK_RV(FN "C_GenerateKey:CKM_PBE_SHA1_DES2_EDE_CBC", rv);if ( rv ) return 1; 

9.If we are not using the password-based encryption switch at program execution, the desired reaction is to perform file encryption using RSA, and hence we will need to generate the secret key value for the operation.

The function FindKeyFromName is part of the SafeNet ProtectToolkit-C CTUTIL library to provide extended functionality.  It is used here to locate the keys which are passed into FCrypt at the command line and return the slot ID, session handle and object handle of those keys.

else {
/* use RSA to encrypt the file */
/* locate encrypting key */
rv = FindKeyFromName(sender, CKO_PRIVATE_KEY,
&hsSlot, &hsSession, &hsKey);if ( rv ) {fprintf( stderr, "Unable to access sender (%s)key\n", sender );CHECK_RV(FN "FindKeyFromName", rv);if ( rv ) return 1; }
/* locate signing key */
rv = FindKeyFromName(recipient, CKO_CERTIFICATE,
&hrSlot, &hrSession, &hrKey);if ( rv ) {rv = FindKeyFromName(recipient, CKO_PUBLIC_KEY,   &hrSlot, &hrSession, &hrKey);
}
if ( rv ) {
fprintf( stderr, "Unable to access recipient (%s) key\n", recipient );CHECK_RV(FN "FindKeyFromName", rv);if ( rv ) return 1;} 

10.To achieve acceptable performance during file encryption and decryption we need to use a symmetric key cipher such as DES.  The DES key we generate for this purpose is to be wrapped with the recipient’s RSA key so it can later be unwrapped and used for decryption without the value of the key ever being known.

a.Rather than simply using the same key for each file encryption, we will generate a random DES key for each encryption of the input file.  The mechanism used here is CKM_DES2_KEY_GEN that is used for generating double-length DES keys.

b.The key wrapping is performed with the C_WrapKey function that encrypts (wraps) a private or secret key.  The function requires the session handle, the wrapping mechanism, the handle of the wrapping key, the handle of the key to be wrapped, a pointer to the location that receives the wrapped key and a pointer to the location that receives the length of the wrapped key.

c.For the wrapping mechanism we will choose CKM_RSA_PKCS that is a multi-purpose mechanism based on the RSA public-key cryptosystem and the block formats defined in PKCS #1. It supports single-part encryption and decryption, single-part signatures and verification with and without message recovery, key wrapping and key unwrapping.

/* create a random des key for the encryption */
memset(&mech,0,sizeof(mech));
mech.mechanism = CKM_DES2_KEY_GEN;
/* generate the key */
rv = C_GenerateKey(hrSession, &mech,
wrappedKeyTemp, NUMITEMS(wrappedKeyTemp), &hKey);
CHECK_RV(FN "C_GenerateKey", rv);
if ( rv ) return 1; 
/* wrap the encryption key with the recipients public key */
memset(&mech,0,sizeof(mech));
mech.mechanism = CKM_RSA_PKCS;
memset(wrappedKey,0,sizeof(wrappedKey));
wrappedKeyLen = sizeof(wrappedKey);
rv = C_WrapKey(hrSession, &mech, hrKey, hKey,
wrappedKey, &wrappedKeyLen); 
CHECK_RV(FN "C_WrapKey", rv);
if ( rv ) return 1; 

11.Now that we have a random secret key to perform the encryption with, we will need to set the required mechanism and parameters prior to encrypting the input file.  As a mechanism for the encryption we will choose CKM_DES3_CBC_PAD which is using triple-DES in Cipher Block Chaining mode and PKCS#1 padding.

An application cannot call C_Encrypt in a session without having called C_EncryptInit first to activate an encryption operation.  C_EncryptInit requires the session’s handle, a pointer to the encryption mechanism and the handle of the encryption key.

In the same manner as we initialized and set up, our digest operation is to be the signature verification to send along to the recipient with the encrypted data.  The mechanism used for our digest is SHA-1 that is defined in PKCS#11 terms as CKM_SHA_1.

/* set up the encryption operation using the random key */
memset(&mech, 0, sizeof(CK_MECHANISM));
mech.mechanism = CKM_DES3_CBC_PAD;
memset(iv, 0, sizeof(iv));
mech.pParameter = iv;
mech.parameterLen = sizeof(iv);
rv = C_EncryptInit(hrSession, &mech, hKey);
CHECK_RV(FN"C_EncryptInit", rv);
if ( rv ) return 1;
/* Set up the digest operation */
memset(&mech, 0, sizeof(CK_MECHANISM));
mech.mechanism = CKM_SHA_1;
rv = C_DigestInit(hrSession, &mech);
CHECK_RV(FN "C_DigestInit", rv);
if ( rv ) return 1;

12.We are now ready to process our input file by encrypting the data, generating the message digest and writing the output to file.

/*
** Process the file.
*/
{
FILE * ifp; /* input */
FILE * ofp; /* output */
CK_SIZE curLen;
CK_SIZE slen;
unsigned char buffer[10 * 1024];
unsigned char encbuffer[10 * 1024];
unsigned int br; /* bytes read */
unsigned int totbw; /* total bytes written */
/* open input and output file pointers */
ifp = fopen(ifile, "rb");
if ( ifp == NULL ) {
fprintf( stderr, "Cannot open %s for input\n",ifile ); return -1;
}
ofp = fopen(ofile, "wb");
if ( ofp == NULL ) { fprintf( stderr, "Cannot open %s for input\n",ofile ); return -1; }

If the password based encryption switch wasn’t set, the first instance we write to file is the DES secret key wrapped by the recipient’s public key. 

if ( ! pflag ) {/* write the encrypted key to the output file */encodedSize = htonl((unsigned long) wrappedKeyLen);br = fwrite(&encodedSize, 1, sizeof(encodedSize), ofp);br = fwrite(wrappedKey, 1, (int)wrappedKeyLen, ofp);
} 
/* get the file length */
{ 
struct _stat buf;
int result;
result = _fstat( _fileno(ifp), &buf );
if( result != 0 ) {
fprintf( stderr, "Cannot get file size for %s\n",
ofile );
return -1;
}
fileSize = buf.st_size;
/*
fileSize = _filelength(_fileno(ifp));
*/
}
fileSize = (fileSize + 8) & ~7; /* round up for padding */
/* write file size to output file */
encodedSize = htonl(fileSize); /* big endian */
br = fwrite(&encodedSize, 1, sizeof(encodedSize), ofp);

13.Since our mode of encryption is cipher block chaining (CBC) we need to perform our output using four definitive looping steps until our data is processed.

a.For the digest we use the PKCS#11 function C_Digest_Update, which continues a multiple-part message-digesting operation, processing another data part.  The function requires the session handle, a pointer to the data part and the length of the data part.

b.For the encryption, we use C_EncryptUpdate, which continues a multiple-part encryption operation, processing another data part. The function requires the session handle, a pointer to the data part; the length of the data part; a pointer to the location that receives the encrypted data part and a pointer to the location that holds the length in bytes of the encrypted data part.

/* read, encrypt, digest and write the cipher text in chunks
*/ totbw = 0;
for ( ;; ) {
br = fread(buffer, 1, sizeof(buffer), ifp);
if ( br == 0 )
break;
/* digest */
rv = C_DigestUpdate(hrSession, buffer, (CK_SIZE)br); CHECK_RV(FN "C_DigestUpdate", rv);
if ( rv ) return 1;
/* encrypt */
curLen = sizeof(encbuffer);
rv = C_EncryptUpdate(hrSession, buffer, (CK_SIZE)br,   encbuffer, &curLen); 
CHECK_RV(FN "C_EncryptUpdate", rv);
if ( rv ) return 1;
/* write cipher text */
br = fwrite(encbuffer, 1, (int)curLen, ofp);
totbw += br;} 

14.Once all the data has been processed, we need to finalize the encryption and digest operation. 

a.To finish the encryption, we use the C_EncryptFinal call, which finishes a multiple-part encryption operation.  The function requires the session handle, a pointer to the location that receives the last encrypted data part, if any, and a pointer to the location that holds the length of the last encrypted data part.

b.For finalizing the digest, we call C_DigestFinal, which finishes a multiple-part message-digesting operation, returning the message digest.  The function requires the session’s handle, a pointer to the location that receives the message digest and a pointer to the location that holds the length of the message digest.

/* finish off the encryption */
curLen = sizeof(encbuffer);
rv = C_EncryptFinal(hrSession, encbuffer, &curLen);
CHECK_RV(FN "C_EncryptFinal", rv);
if ( rv ) return 1;
if ( curLen ) {
br = fwrite(encbuffer, 1, (int)curLen, ofp);
totbw += br;}
if ( totbw != fileSize ) {
fprintf( stderr, "size prediction incorrect %ld,
%ld\n", totbw, fileSize );} 
/* finish off the digest */
len = sizeof(digest);
rv = C_DigestFinal(hrSession, digest, &len);
CHECK_RV(FN "C_DigestFinal", rv);
if ( rv ) return 1; 

15.If the password-based encryption flag was set, we use the digest created in the above process as our signature, since there is no recipient key to sign the data with.  For our DES encryption we will sign the digest with our recipient’s public key.

a.The function C_SignInit is our first call and initializes a signature operation, where the signature is an appendix to the data.  The function requires the session’s handle, a pointer to the signature mechanism and the handle of the signature key.

b.We also need to specify a mechanism to use for our signature operation, in this case CKM_RSA_PKCS, which is an RSA PKCS #1 mechanism.

c.The signature generation is performed with the call to C_Sign that signs data in a single part, where the signature is an appendix to the data.  The function requires the session’s handle, a pointer to the data, the length of the data, a pointer to the location that receives the signature, and a pointer to the location that holds the length of the signature.

if ( pflag ) {
slen = len;
memcpy(signature, digest, slen);
} else {/* Set up the signature operation */memset(&mech, 0, sizeof(CK_MECHANISM));mech.mechanism = CKM_RSA_PKCS;rv = C_SignInit(hsSession, &mech, hsKey);CHECK_RV(FN "C_SignInit", rv);if ( rv ) return 1;slen = sizeof(signature);rv = C_Sign(hsSession, digest, len, signature, &slen);CHECK_RV(FN "C_SignInit", rv);if ( rv ) return 1; }
/* write the signature to the file */
encodedSize = htonl((unsigned long) slen);
br = fwrite(&encodedSize, 1, sizeof(encodedSize), ofp);
br = fwrite(signature, 1, (int)slen, ofp);
/* clean up */
fclose(ifp);
fclose(ofp);
}
C_CloseSession(hrSession);
C_CloseSession(hsSession);
return 0;
}

Next, see Decrypt Function.