This page gives example code in Visual Basic (VB6 and VB.NET/VB200x) using CryptoSys PKI to do RSA encryption in the manner suggested by Ferguson and Schneier in their book Practical Cryptography (Wiley, 2003). The algorithm is described on our RSA Algorithm page.
We use the 1024-bit RSA key pair belonging to Bob from RFC 4134 Examples of S/MIME Messages. Bob's public key is in his X.509 certificate and his private key is in an encrypted PKCS#8 file. The complete files are in the download below. Yes, we know this public key does not have an exponent of 5 as suggested, but you get the idea. The password for the encrypted private key is, as always, "password".
We take a simple text message of 60 bytes "Hello, world! This is a secret message. For Bob's eyes only.", derive a content encryption key (CEK) from a randomly-generated value, encrypt this using RSA, and use the CEK to encrypt the text message using conventional AES-128 (this is much faster than RSA).
We pass the information to the recipient, Bob, as a concatenated string of hex characters; we could have used base64 which would be slightly shorter, but hex characters are easier to manipulate for the demo. You could, of course, pass the information in binary form, or wrap it up in ASN.1 encoding, or XML. It's your choice to agree with the recipient so long as they know how to interpret it.
To decrypt we follow the reverse process. This is just a demonstration program outlining the technique. Obviously in practice you'd split it up into sub-routines with suitable parameters (and add better error handling).
VB6/VBA
Public Sub Ferguson_Schneier_Encrypt() ' Ref: Niels Ferguson and Bruce Schneier, Practical Cryptography, Wiley, 2003 Dim nRet As Long Dim strPublicKey As String Dim strCert As String Dim nKeyBytes As Long Dim abR() As Byte Dim abC() As Byte Dim abCEK() As Byte Dim abData() As Byte Dim abMessage() As Byte Dim strMessage As String Dim abIV() As Byte Dim nMsgLen As Long Dim nDataLen As Long Dim strTransmission As String Dim strCorrect As String ' 1. ENCRYPTION ' INPUT: strCert = "BobRSASignByCarl.cer" strMessage = "Hello, world! This is a secret message. For Bob's eyes only." strCorrect = strMessage Debug.Print "ENCRYPTING..." Debug.Print "m='" & strMessage & "'" ' 1.1 Generate content encryption key, CEK, and RSA-encrypted CEK, c. ' 1.1.1 Read in Bob's public key to internal string strPublicKey = rsaGetPublicKeyFromCert(strCert) Debug.Print "Public key length k=|n|=" & RSA_KeyBits(strPublicKey) & " bits" ' How many bytes in the key? nKeyBytes = RSA_KeyBytes(strPublicKey) If nKeyBytes <= 0 Then Exit Sub ' CATCH ERROR ' 1.1.2 Generate a random number r of the same length ReDim abR(nKeyBytes - 1) nRet = RNG_Bytes(abR(0), nKeyBytes, "", 0) ' and make sure it is less than n by making the two most-significant byte zero ' (we will use this as a check later) abR(0) = 0 abR(1) = 0 Debug.Print "r=" & cnvHexStrFromBytes(abR) ' 1.1.3 Compute CEK by hashing r: CEK = Hash(r) abCEK = Hash256Double(abR) Debug.Print "CEK=" & cnvHexStrFromBytes(abCEK) ' 1.1.4 Compute c = r^e mod n abC = abR nRet = RSA_RawPublic(abC(0), nKeyBytes, strPublicKey, 0) If nRet <> 0 Then Exit Sub ' CATCH ERROR Debug.Print "c=" & cnvHexStrFromBytes(abC) ' 1.2 Encrypt message using symmetrical AES-128 block cipher in CBC mode ' 1.2.1 Convert the message string to an array of bytes abMessage = StrConv(strMessage, vbFromUnicode) nMsgLen = UBound(abMessage) + 1 Debug.Print "m= " & cnvHexStrFromBytes(abMessage) ' 1.2.2 Pad this to an exact multiple of the AES block size nDataLen = PAD_BytesBlock(vbNull, 0, abMessage(0), nMsgLen, PKI_BLK_AES_BYTES, 0) If nDataLen <= 0 Then Exit Sub ' CATCH ERROR ReDim abData(nDataLen - 1) nDataLen = PAD_BytesBlock(abData(0), nDataLen, abMessage(0), nMsgLen, PKI_BLK_AES_BYTES, 0) Debug.Print "PT=" & cnvHexStrFromBytes(abData) ' 1.2.3 Generate a random IV of same size as AES block (16 bytes) ReDim abIV(PKI_BLK_AES_BYTES - 1) nRet = RNG_Bytes(abIV(0), PKI_BLK_AES_BYTES, "", 0) Debug.Print "IV=" & cnvHexStrFromBytes(abIV) ' 1.2.4 Encrypt the plain text using AES-128 with the content encryption key: CT=Encrypt(CEK, m) nRet = CIPHER_Bytes(ENCRYPT, abData(0), abData(0), nDataLen, abCEK(0), abIV(0), "aes128-cbc", 0) If nRet <> 0 Then Exit Sub ' CATCH ERROR Debug.Print "CT=" & cnvHexStrFromBytes(abData) ' 1.3 Send transmission to recipient ' We need to transmit c || IV || CT to Bob. We'll send a concatenated hex string strTransmission = cnvHexStrFromBytes(abC) & cnvHexStrFromBytes(abIV) & cnvHexStrFromBytes(abData) Debug.Print Debug.Print "TRANSMIT=" & strTransmission Debug.Print ' 2. DECRYPTION Dim strKeyFile As String Dim strPassword As String Dim strPrivateKey As String Dim strC_Hex As String Dim strIV_Hex As String Dim strCT_Hex As String Dim nLen As Long Dim nOffset As Long ' INPUT: strKeyFile = "BobPrivRSAEncrypt.epk" ' Bob's encrypted private key file strPassword = "password" Debug.Print "DECRYPTING..." ' 2.1 Read in private key strPrivateKey = rsaReadPrivateKey(strKeyFile, strPassword) Debug.Print "Private key length k=|n|=" & RSA_KeyBits(strPrivateKey) & " bits" ' and get its size in bytes nKeyBytes = RSA_KeyBytes(strPublicKey) If nKeyBytes <= 0 Then Exit Sub ' CATCH ERROR ' We are done with password, so wipe it securely (and don't hard-code it like we did!) strPassword = wipeString(strPassword) ' 2.2 Parse input string: c || IV || CT ' The first nKeyBytes are c, two hex digits per byte nOffset = 1 nLen = nKeyBytes * 2 strC_Hex = Mid(strTransmission, nOffset, nLen) ' The next 16 bytes are the IV nOffset = nOffset + nLen nLen = PKI_BLK_AES_BYTES * 2 strIV_Hex = Mid(strTransmission, nOffset, nLen) ' And the remainder is the ciphertext, CT nOffset = nOffset + nLen strCT_Hex = Mid(strTransmission, nOffset) ' Convert these to byte arrays abC = cnvBytesFromHexStr(strC_Hex) Debug.Print "c=" & cnvHexStrFromBytes(abC) abIV = cnvBytesFromHexStr(strIV_Hex) Debug.Print "IV=" & cnvHexStrFromBytes(abIV) abData = cnvBytesFromHexStr(strCT_Hex) Debug.Print "CT=" & cnvHexStrFromBytes(abData) ' 2.3 Decrypt c to obtain r abR = abC nRet = RSA_RawPrivate(abR(0), nKeyBytes, strPrivateKey, 0) If nRet <> 0 Then Exit Sub ' CATCH ERROR Debug.Print "r=" & cnvHexStrFromBytes(abR) ' We are done with private key string, so wipe it securely strPrivateKey = wipeString(strPrivateKey) ' 2.3.1 Check that first two bytes are zero (1/65536 chance of false positive) If abR(0) <> 0 Or abR(1) <> 0 Then Exit Sub ' CATCH ERROR ' 2.4 Obtain CEK by hashing r abCEK = Hash256Double(abR) Debug.Print "CEK=" & cnvHexStrFromBytes(abCEK) ' 2.5 Decrypt the plain text using AES-128 with the content encryption key: m=Decrypt(CEK, CT) nRet = CIPHER_Bytes(DECRYPT, abData(0), abData(0), nDataLen, abCEK(0), abIV(0), "aes128-cbc", 0) If nRet <> 0 Then Exit Sub ' CATCH ERROR Debug.Print "PT=" & cnvHexStrFromBytes(abData) ' 2.5.1 Strip the padding nMsgLen = PAD_UnpadBytes(abData(0), nDataLen, abData(0), nDataLen, PKI_BLK_AES_BYTES, 0) If (nMsgLen <= 0) Then Exit Sub ReDim Preserve abData(nMsgLen - 1) Debug.Print "m =" & cnvHexStrFromBytes(abData) ' 2.5.2 Convert byte array back to a string strMessage = StrConv(abData, vbUnicode) Debug.Print "m='" & strMessage & "'" If strMessage <> strCorrect Then Exit Sub ' CATCH ERROR Debug.Print "OK, all done successfully" End Sub Public Function Hash256Double(abInput() As Byte) As Variant ' Computes SHA256(SHA256(input)) Dim nRet As Long Dim nInputLen As Long Dim abDigest() As Byte ReDim abDigest(PKI_SHA256_BYTES - 1) nInputLen = UBound(abInput) + 1 nRet = HASH_Bytes(abDigest(0), PKI_SHA256_BYTES, abInput(0), nInputLen, PKI_HASH_SHA256) nRet = HASH_Bytes(abDigest(0), PKI_SHA256_BYTES, abDigest(0), PKI_SHA256_BYTES, PKI_HASH_SHA256) Hash256Double = abDigest End Function
Output
Note that the output will be different each time the program is run because of the random values used in the algorithm. This is by design.
ENCRYPTING... m='Hello, world! This is a secret message. For Bob's eyes only.' Public key length k=|n|=1024 bits r=0000481DD2EEB162F18BE26BF64BB980FF99134C39F1443823811D837370F8FAE4364C3171CEE355082118BC9EF97779B9DEB6FBAC33E2DCB4F7B6AB5299B1661AD5CDEF8C76B72A1F0D85A1079B96F7C293C029AFD2442FB65AFB370FE7A971A235C3BDD866C8242C15BB4B823696FF51681C012905101F1D643A4AC057DECE CEK=6670B591275D5B9946741A1BFA3755068831A4B696AF7DA39FBAF208D99ADF8C c=0BF2770071E690C2327729978037F9BE5513A594A6F9CD25C4834CF51C4277414991EC3871B0183FB183636A28B4294D48766E6AC69061A84B4D5E192143CFBEC3ABFEE56501F7D1A2C544A84077DD3C8AC8B390A34214FDBFC8964CC10252E4EA6936F8430BCC0355A67C33C0307F1D3E4A9459A323745DB64B541A61CB9977 m= 48656C6C6F2C20776F726C64212054686973206973206120736563726574206D6573736167652E20466F7220426F6227732065796573206F6E6C792E PT=48656C6C6F2C20776F726C64212054686973206973206120736563726574206D6573736167652E20466F7220426F6227732065796573206F6E6C792E04040404 IV=84D8A4C56B5EA779A42D358081F940A2 CT=8487F05119052DA340612DA5F70E5CD590854A5C568405DB14EF7FB09939D5E64CC2180BD8923E6785079040D815709FCF9DB188BBB71FCA3D5943EB10B25C78 TRANSMIT=0BF2770071E690C2327729978037F9BE5513A594A6F9CD25C4834CF51C4277414991EC3871B0183FB183636A28B4294D48766E6AC69061A84B4D5E192143CFBEC3ABFEE56501F7D1A2C544A84077DD3C8AC8B390A34214FDBFC8964CC10252E4EA6936F8430BCC0355A67C33C0307F1D3E4A9459A323745DB64B541A61CB997784D8A4C56B5EA779A42D358081F940A28487F05119052DA340612DA5F70E5CD590854A5C568405DB14EF7FB09939D5E64CC2180BD8923E6785079040D815709FCF9DB188BBB71FCA3D5943EB10B25C78 DECRYPTING... Private key length k=|n|=1024 bits c=0BF2770071E690C2327729978037F9BE5513A594A6F9CD25C4834CF51C4277414991EC3871B0183FB183636A28B4294D48766E6AC69061A84B4D5E192143CFBEC3ABFEE56501F7D1A2C544A84077DD3C8AC8B390A34214FDBFC8964CC10252E4EA6936F8430BCC0355A67C33C0307F1D3E4A9459A323745DB64B541A61CB9977 IV=84D8A4C56B5EA779A42D358081F940A2 CT=8487F05119052DA340612DA5F70E5CD590854A5C568405DB14EF7FB09939D5E64CC2180BD8923E6785079040D815709FCF9DB188BBB71FCA3D5943EB10B25C78 r=0000481DD2EEB162F18BE26BF64BB980FF99134C39F1443823811D837370F8FAE4364C3171CEE355082118BC9EF97779B9DEB6FBAC33E2DCB4F7B6AB5299B1661AD5CDEF8C76B72A1F0D85A1079B96F7C293C029AFD2442FB65AFB370FE7A971A235C3BDD866C8242C15BB4B823696FF51681C012905101F1D643A4AC057DECE CEK=6670B591275D5B9946741A1BFA3755068831A4B696AF7DA39FBAF208D99ADF8C PT=48656C6C6F2C20776F726C64212054686973206973206120736563726574206D6573736167652E20466F7220426F6227732065796573206F6E6C792E04040404 m =48656C6C6F2C20776F726C64212054686973206973206120736563726574206D6573736167652E20466F7220426F6227732065796573206F6E6C792E m='Hello, world! This is a secret message. For Bob's eyes only.' OK, all done successfully
VB.NET/VB200x
Imports CryptoSysPKI
Imports System.Text
Module RSA_FergusonSchneier
Sub Main()
Call Ferguson_Schneier_Encrypt()
End Sub
Public Sub Ferguson_Schneier_Encrypt()
' Ref: Niels Ferguson and Bruce Schneier, Practical Cryptography, Wiley, 2003
Dim strPublicKey As String
Dim strCert As String
Dim nKeyBytes As Integer
Dim abR() As Byte
Dim abC() As Byte
Dim abCEK() As Byte
Dim abData() As Byte
Dim abMessage() As Byte
Dim strMessage As String
Dim abIV() As Byte
Dim nMsgLen As Integer
Dim strTransmission As String
Dim strCorrect As String
' 1. ENCRYPTION
' INPUT:
strCert = "BobRSASignByCarl.cer"
strMessage = "Hello, world! This is a secret message. For Bob's eyes only."
strCorrect = strMessage
Console.WriteLine("ENCRYPTING...")
Console.WriteLine("m='" & strMessage & "'")
' 1.1 Generate content encryption key, CEK, and RSA-encrypted CEK, c.
' 1.1.1 Read in Bob's public key to internal string
strPublicKey = Rsa.GetPublicKeyFromCert(strCert).ToString
Console.WriteLine("Public key length k=|n|=" & Rsa.KeyBits(strPublicKey) & " bits")
' How many bytes in the key?
nKeyBytes = Rsa.KeyBytes(strPublicKey)
If nKeyBytes <= 0 Then Exit Sub ' CATCH ERROR
' 1.1.2 Generate a random number r of the same length
abR = Rng.Bytes(nKeyBytes)
' and make sure it is less than n by making the two most-significant byte zero
' (we will use this as a check later)
abR(0) = 0
abR(1) = 0
Console.WriteLine("r=" & Cnv.ToHex(abR))
' 1.1.3 Compute CEK by hashing r: CEK = Hash(r)
abCEK = Hash256Double(abR)
Console.WriteLine("CEK=" & Cnv.ToHex(abCEK))
' 1.1.4 Compute c = r^e mod n
abC = Rsa.RawPublic(abR, strPublicKey)
If abC.Length = 0 Then Exit Sub ' CATCH ERROR
Console.WriteLine("c=" & Cnv.ToHex(abC))
' 1.2 Encrypt message using symmetrical AES-128 block cipher in CBC mode
' 1.2.1 Convert the message string to an array of bytes
abMessage = System.Text.Encoding.Default.GetBytes(strMessage)
nMsgLen = UBound(abMessage) + 1
Console.WriteLine("m= " & Cnv.ToHex(abMessage))
' 1.2.2 Pad this to an exact multiple of the AES block size
abData = Cipher.Pad(abMessage, CipherAlgorithm.Aes128)
Console.WriteLine("PT=" & Cnv.ToHex(abData))
' 1.2.3 Generate a random IV of same size as AES block (16 bytes)
abIV = Rng.Bytes(16)
Console.WriteLine("IV=" & Cnv.ToHex(abIV))
' 1.2.4 Encrypt the plain text using AES-128 with the content encryption key: CT=Encrypt(CEK, m)
abData = Cipher.Encrypt(abData, abCEK, abIV, CipherAlgorithm.Aes128, Mode.CBC)
If abData.Length = 0 Then Exit Sub ' CATCH ERROR
Console.WriteLine("CT=" & Cnv.ToHex(abData))
' 1.3 Send transmission to recipient
' We need to transmit c || IV || CT to Bob. We'll send a concatenated hex string
strTransmission = Cnv.ToHex(abC) & Cnv.ToHex(abIV) & Cnv.ToHex(abData)
Console.WriteLine()
Console.WriteLine("TRANSMIT=" & strTransmission)
Console.WriteLine()
' 2. DECRYPTION
Dim strKeyFile As String
Dim sbPassword As StringBuilder
Dim sbPrivateKey As StringBuilder
Dim strC_Hex As String
Dim strIV_Hex As String
Dim strCT_Hex As String
Dim nLen As Integer
Dim nOffset As Integer
' INPUT:
strKeyFile = "BobPrivRSAEncrypt.epk" ' Bob's encrypted private key file
sbPassword = New StringBuilder("password")
Console.WriteLine("DECRYPTING...")
' 2.1 Read in private key
sbPrivateKey = Rsa.ReadEncPrivateKey(strKeyFile, sbPassword.ToString)
Console.WriteLine("Private key length k=|n|=" & Rsa.KeyBits(sbPrivateKey.ToString) & " bits")
' and get its size in bytes
nKeyBytes = Rsa.KeyBytes(sbPrivateKey.ToString)
If nKeyBytes <= 0 Then Exit Sub ' CATCH ERROR
' We are done with password, so wipe it securely (and don't hard-code it like we did!)
Wipe.String(sbPassword)
' 2.2 Parse input string: c || IV || CT
' The first nKeyBytes are c, two hex digits per byte
nOffset = 1
nLen = nKeyBytes * 2
strC_Hex = Mid(strTransmission, nOffset, nLen)
' The next 16 bytes are the IV
nOffset = nOffset + nLen
nLen = 16 * 2
strIV_Hex = Mid(strTransmission, nOffset, nLen)
' And the remainder is the ciphertext, CT
nOffset = nOffset + nLen
strCT_Hex = Mid(strTransmission, nOffset)
' Convert these to byte arrays
abC = Cnv.FromHex(strC_Hex)
Console.WriteLine("c=" & Cnv.ToHex(abC))
abIV = Cnv.FromHex(strIV_Hex)
Console.WriteLine("IV=" & Cnv.ToHex(abIV))
abData = Cnv.FromHex(strCT_Hex)
Console.WriteLine("CT=" & Cnv.ToHex(abData))
' 2.3 Decrypt c to obtain r
abR = Rsa.RawPrivate(abC, sbPrivateKey.ToString)
If abR.Length = 0 Then Exit Sub ' CATCH ERROR
Console.WriteLine("r=" & Cnv.ToHex(abR))
' We are done with private key string, so wipe it securely
Wipe.String(sbPrivateKey)
' 2.3.1 Check that first two bytes are zero (1/65536 chance of false positive)
If abR(0) <> 0 Or abR(1) <> 0 Then Exit Sub ' CATCH ERROR
' 2.4 Obtain CEK by hashing r
abCEK = Hash256Double(abR)
Console.WriteLine("CEK=" & Cnv.ToHex(abCEK))
' 2.5 Decrypt the plain text using AES-128 with the content encryption key: m=Decrypt(CEK, CT)
abData = Cipher.Decrypt(abData, abCEK, abIV, CipherAlgorithm.Aes128, Mode.CBC)
If abData.Length = 0 Then Exit Sub ' CATCH ERROR
Console.WriteLine("PT=" & Cnv.ToHex(abData))
' 2.5.1 Strip the padding
abData = Cipher.Unpad(abData, CipherAlgorithm.Aes128)
If abData.Length = 0 Then Exit Sub
Console.WriteLine("m =" & Cnv.ToHex(abData))
' 2.5.2 Convert byte array back to a string
strMessage = System.Text.Encoding.Default.GetString(abData)
Console.WriteLine("m='" & strMessage & "'")
If strMessage <> strCorrect Then Exit Sub ' CATCH ERROR
Console.WriteLine("OK, all done successfully")
End Sub
Public Function Hash256Double(ByVal abInput() As Byte) As Object
' Computes SHA256(SHA256(input))
Dim abDigest() As Byte
abDigest = Hash.BytesFromBytes(abInput, HashAlgorithm.Sha256)
abDigest = Hash.BytesFromBytes(abDigest, HashAlgorithm.Sha256)
Hash256Double = abDigest
End Function
End Module
Here is the above source code and Bob's key material: zipped, 6 kB.
To contact us or comment on this page, please send us a message.
This page first published by DI Management 20 November 2010. Last updated 10 September 2025.