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 14 October 2012. Reformatted for HTML5 on 12 June 2020.