This is an updated extract from the CryptoSys PKI Toolkit Manual.
Encrypting and signing with RSA | Examples in VB6 | More Techniques | Key Security Considerations | References | Contact
The original intention of the CryptoSys PKI Toolkit was to provide a set of primitives to carry out S/MIME operations using relatively high-level "Cryptographic Message Syntax" functions. However, we get so many queries about using the "raw" RSA functions that we've added this section on techniques.
The functions RSA_RawPublic
and RSA_RawPrivate
just carry out the basic RSA encryption or decryption operation on a "raw" block of data.
The block must be exactly the same length in bytes as the length of the RSA key modulus;
it must obey certain mathematical properties (in practice, make sure the first byte is zero);
and it should be formatted in a certain way to improve security and make it easier to
pass to other systems (.NET's native cryptography functions hide this part of the process from you).
Encryption and signing use the same RSA operations:
Use the function RSA_EncodeMsg
to encode or "pad" the message data you want to
encryt or sign.
Remember that Encoding is Not Encryption.
For more information on how the RSA key data is stored and how the various functions work together, see RSA Key Formats. For some examples in encryption and signing in C#, see Doing RSA Encryption and Signing with the C# code.
This sample code shows how to encrypt and decrypt a short message directly and suggests a simple pair of 'Encrypt' and 'Decrypt' functions (without any error handling).
Note that RSA is normally only used to encrypt a session key prior to using a much faster symmetric block cipher algorithm. Similarly it is usually used just to sign a hash of the message rather than the message itself. The size of the "message" in both cases is limited by the length of the RSA key less the padding bytes.
Public Function rsaEncryptBytes(abMessage() As Byte, strPublicKey As String) As Variant ' Uses RSA public key to encrypt a message using default PKCS-1-v1.5 encoding method ' Returns the resulting ciphertext as an array of bytes passed as a Variant, ' or an empty value on error. Dim lngRet As Long Dim abDummy(0) As Byte Dim abBlock() As Byte ' All lengths are in octets (i.e. 8-bit bytes) Dim nkLen As Long ' length, k, of RSA key modulus Dim nmLen As Long ' length of message, M rsaEncryptBytes = abDummy ' Compute lengths nmLen = BytesLength(abMessage) nkLen = RSA_KeyBytes(strPublicKey) ' Encode using EME ReDim abBlock(nkLen - 1) lngRet = RSA_EncodeMsg(abBlock(0), nkLen, abMessage(0), nmLen, PKI_EME_DEFAULT) If lngRet <> 0 Then Exit Function ' Encrypt using RSA public key lngRet = RSA_RawPublic(abBlock(0), nkLen, strPublicKey, 0) If lngRet <> 0 Then Exit Function ' Return ciphertext block rsaEncryptBytes = abBlock End Function Public Function rsaDecryptBytes(abCipher() As Byte, strPrivateKey As String) As Variant ' Decrypts RSA-encrypted ciphertext using the RSA private key passed as a string. ' Returns the resulting message as an array of bytes passed as a Variant, ' or an empty value on error. Dim abDummy(0) As Byte Dim abMessage() As Byte Dim lngRet As Long Dim abBlock() As Byte Dim nkLen As Long ' length, k, of RSA key modulus Dim nmLen As Long ' length of message, M rsaDecryptBytes = abDummy nkLen = BytesLength(abCipher) If nkLen <> RSA_KeyBytes(strPrivateKey) Then Exit Function End If abBlock = abCipher ' Decrypt lngRet = RSA_RawPrivate(abBlock(0), nkLen, strPrivateKey, 0) If lngRet <> 0 Then Exit Function End If ' Now we decode according to EME-PKCS-V1_5-DECODE nmLen = RSA_DecodeMsg(0, 0, abBlock(0), nkLen, PKI_EME_DEFAULT) If nmLen <= 0 Then Exit Function ReDim abMessage(nmLen - 1) nmLen = RSA_DecodeMsg(abMessage(0), nmLen, abBlock(0), nkLen, PKI_EME_DEFAULT) ' Output M. rsaDecryptBytes = abMessage End Function Private Function BytesLength(abBytes() As Byte) As Long ' General function to return length of byte array ' Trap error if array is empty On Error Resume Next BytesLength = UBound(abBytes) - LBound(abBytes) + 1 End Function Public Function TestEncryptAndDecrypt() Dim abMessage() As Byte Dim abBlock() As Byte Dim strPublicKey As String Dim strPubKeyFile As String strPubKeyFile = "mykeypub.bin" ' Convert ANSI text to bytes abMessage = StrConv("Hello world!", vbFromUnicode) Debug.Print "M (ansi): " & StrConv(abMessage, vbUnicode) Debug.Print "M (hex): " & cnvHexStrFromBytes(abMessage) ' Read in the public key from file strPublicKey = rsaReadPublicKey(strPubKeyFile) If Len(strPublicKey) = 0 Then MsgBox "Cannot read RSA public key file '" & strPubKeyFile & "'", vbCritical Exit Function End If ' Call encrypt function abBlock = rsaEncryptBytes(abMessage, strPublicKey) Debug.Print "CT: " & cnvHexStrFromBytes(abBlock) ' DECRYPTION Dim strPrivateKey As String Dim strPriKeyFile As String Dim abPlain() As Byte strPriKeyFile = "mykeypri.bin" ' Read in the private key from file strPrivateKey = rsaReadPrivateKey(strPriKeyFile, "password") If Len(strPrivateKey) = 0 Then MsgBox "Cannot read RSA private key file '" & strPriKeyFile & "'", vbCritical Exit Function End If ' Call decrypt function abPlain = rsaDecryptBytes(abBlock, strPrivateKey) Debug.Print "PT (hex) : " & cnvHexStrFromBytes(abPlain) Debug.Print "PT (ansi): " & StrConv(abPlain, vbUnicode) End Function
This should produce results similar to this
M (ansi): Hello world! M (hex): 48656C6C6F20776F726C6421 CT: 4866117CDA08F3E93B39230685BC02... PT (hex) : 48656C6C6F20776F726C6421 PT (ansi): Hello world!
Note that the encoding for encryption includes random bytes so the resulting ciphertext will be different each time
The next example shows how to sign a message.
Unlike the encryption example, the RSA_EncodeMsg
function
always creates a message digest hash of the original data when in "signing" mode.
When verifying, you could use the RSA_DecodeMsg
function to extract the message digest hash and then create a separate digest of the original text.
But it's much simpler just to use the RSA_EncodeMsg
function on the original data
and compare the full blocks instead.
Dim abMessage() As Byte Dim abCmpBlock() As Byte Dim strPublicKey As String Dim strPubKeyFile As String Dim strPrivateKey As String Dim strPriKeyFile As String Dim lngRet As Long Dim abBlock() As Byte Dim nkLen As Long ' length, k, of RSA key modulus Dim nmLen As Long ' length of message, M strPubKeyFile = "mykeypub.bin" strPriKeyFile = "mykeypri.bin" ' PART 1. Encode and sign a message using the private key ' Convert ANSI text to bytes abMessage = StrConv("Hello world!", vbFromUnicode) Debug.Print "M (ansi): " & StrConv(abMessage, vbUnicode) Debug.Print "M (hex): " & cnvHexStrFromBytes(abMessage) ' Read in the private key from encrypted file strPrivateKey = rsaReadPrivateKey(strPriKeyFile, "password") If Len(strPrivateKey) = 0 Then MsgBox "Cannot read RSA key file '" & strPriKeyFile & "'", vbCritical Exit Function End If ' To sign: first encode the message, then "encrypt" with RSA ' Compute lengths nmLen = UBound(abMessage) - LBound(abMessage) + 1 nkLen = RSA_KeyBytes(strPrivateKey) Debug.Print "Key is " & nkLen & " bytes long" Debug.Print "Message is " & nmLen & " bytes long" ' Encode for signature ReDim abBlock(nkLen - 1) lngRet = RSA_EncodeMsg(abBlock(0), nkLen, abMessage(0), nmLen, PKI_EMSIG_DEFAULT) Debug.Print "RSA_EncodeMsg returns " & lngRet & " (expected 0)" Debug.Print "EM: " & cnvHexStrFromBytes(abBlock) ' Sign using RSA private key lngRet = RSA_RawPrivate(abBlock(0), nkLen, strPrivateKey, 0) Debug.Print "SG: " & cnvHexStrFromBytes(abBlock) ' Clear the private key for security Call WIPE_String(strPrivateKey, Len(strPrivateKey)) ' PART 2. Verify with public key ' Read in the public key from file strPublicKey = rsaReadPublicKey(strPubKeyFile) If Len(strPublicKey) = 0 Then MsgBox "Cannot read RSA key file '" & strPubKeyFile & "'", vbCritical Exit Function End If ' Check its length If RSA_KeyBytes(strPublicKey) <> nkLen Then MsgBox "Key lengths of private and public key don't match", vbCritical Exit Function End If ' Signed data is in abBlock, so "decrypt" it lngRet = RSA_RawPublic(abBlock(0), nkLen, strPublicKey, 0) If lngRet <> 0 Then MsgBox "Decryption error", vbCritical Exit Function End If Debug.Print "EM: " & cnvHexStrFromBytes(abBlock) ' We could use RSA_DecodeMsg here to extract the message digest, ' then compute the message digest independently of the message, ' and then compare the two hashes, but... ' it's simpler just to create another encoded block of the original message ' and compare the full blocks directly ReDim abCmpBlock(nkLen - 1) lngRet = RSA_EncodeMsg(abCmpBlock(0), nkLen, abMessage(0), nmLen, PKI_EMSIG_DEFAULT) Debug.Print "RSA_EncodeMsg returns " & lngRet & " (expected 0)" Debug.Print "EM':" & cnvHexStrFromBytes(abCmpBlock) If StrConv(abCmpBlock, vbUnicode) <> StrConv(abBlock, vbUnicode) Then MsgBox "Verification failed.", vbCritical Exit Function End If Debug.Print "Verification OK."
This should produce output like
M (ansi): Hello world! M (hex): 48656C6C6F20776F726C6421 Key is 64 bytes long Message is 12 bytes long RSA_EncodeMsg returns 0 (expected 0) EM: 0001FFFFFF...FFFFFF003021300906052B0E03021A 05000414D3486AE9136E7856BC42212385EA797094475802 SG: D3C6EC4860... EM: 0001FFFFFF... RSA_EncodeMsg returns 0 (expected 0) EM':0001FFFFFF... Verification OK.
For encryption in practice, except for very short messages, we generate a random session key, encrypt this key using RSA, and then encrypt the plaintext using a faster, symmetric block cipher like Triple DES or AES-128 (hint to implementors: do this in CBC mode, don't use ECB). This session key is sometimes referred to as the Content Encryption Key (CEK). You would then need to transmit a message to your recipient in the form
+----------------------+----+----------------------------------------------+ | CEK-encrypted-by-RSA | IV | Data-encrypted-by-symmetric-cipher-using-CEK | +----------------------+----+----------------------------------------------+
where IV is the initialization Vector for the block cipher encryption, generated uniquely each time. To decrypt, parse the input into its three components, use the RSA private key (held separately) to decrypt the RSA block and hence get the CEK to use to decrypt the main body of data. This technique is as strong as its weakest link. Triple DES with a full 192-bit triple key is equivalent in security to a 2048-bit RSA key. AES-128 is equivalent to a 3072-bit RSA key. See [NIST80057] for more details.
For digital signing, unless the message is very short, we generate a message digest of the original message using a hash function, "encrypt" the digest using the RSA private key, and then send this block on to our recipient as a "digital signature", usually together with the message itself.
+---------------+------------------+---------------------+ |Hash algorithm | Original message | RSA Signature block | +---------------+------------------+---------------------+
To verify, the recipient parses the input into its components and then "decrypts" the RSA block using the sender's public key to recover the message digest. She then independently computes the message digest hash of the received message and compares the two. If they are the same, then the signature has been verified.
CMS_MakeEnvData
and CMS_MakeSigData
.
The primitives in this toolkit allow you to do a lot of low-level operations with RSA keys. The original design only permitted private keys to be stored as a file in encrypted format. In response to many requests from users, we've added various functions that allow you to import and save private keys in a variety of unencrypted formats, including XML and OpenSSL-compatible PEM formats.
Use these functions in your tests by all means, but if you are using this toolkit to make an application to be used by less-experienced end users, follow the following guidelines:
WIPE_Data
function to wipe the
internal private key string as soon as you've finished using it.
WIPE_File
function to erase it immediately afterwards.
Make sure this happens even if an error condition occurs.
For more information or to comment on this page, please send us a message.
This page last updated 15 June 2020