' Module: RSAES_OAEP_example.bas 

' The function `rsaes_oaep_encrypt_example' reproduces the
' WORKED-OUT EXAMPLE FOR RSAES-OAEP from pkcs-1v2-1-vec.zip in the file `oaep-int.txt'
' using functions from the CryptoSys PKI Toolkit <www.cryptosys.net/pki/>
' The function `rsaes_oaep_decrypt_example' below shows how to decrypt the same data.

' The public and private key data have been converted to DER-encoded binary files
' rsa1024pub.bin and rsa1024pri.bin.
' We have created an encrypted version of the private key, rsa1024epk.bin, with password "password"
' (see the function `encryptPrivateKeyInfo' below)

' Ref: pkcs-1v2-1-vec.zip
' Available from: <ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip>
' Also: PKCS #1: RSA Cryptography Specifications Version 2.1 (PKCS-1v2-1)

' RSAEP = RSA Encryption Primitive
' OAEP = Optimal Asymmetric Encryption Primitive

'***************** COPYRIGHT NOTICE *******************
' This code was originally written by David Ireland and
' is copyright (C) 2005 DI Management Services Pty Ltd
' <www.di-mgt.com.au>.
' Provided "as is". No warranties. Use at own risk.
' It is not to be altered or distributed,
' except as part of an application.
' You are free to use it in any application,
' provided this copyright notice is left unchanged.
'************** END OF COPYRIGHT NOTICE ***************

Option Explicit

' IMPORTANT: change this path to suit your system
Private Const TEST_KEY_PATH As String = "C:\Test\"

Public Function rsaes_oaep_encrypt_example()
    
    Dim lngRet As Long
    Dim strMessage As String
    Dim strSeed As String
    Dim abMessage() As Byte
    Dim abSeed() As Byte
    Dim abDB() As Byte
    Dim abLHash() As Byte
    Dim abDbMask() As Byte
    Dim abMaskedDB() As Byte
    Dim abSeedMask() As Byte
    Dim abMaskedSeed() As Byte
    Dim abBlock() As Byte
    ' All lengths are in octets (i.e. 8-bit bytes)
    Dim nhLen As Long   ' length of hash = 20 for SHA-1
    Dim nkLen As Long   ' length, k, of RSA key modulus (= 1024/8 = 128 here)
    Dim nmLen As Long   ' length of message, M
    Dim npsLen As Long  ' length of padding string, PS
    Dim ndbLen As Long  ' length of data block, DB
    Dim i As Long
    Dim iOffset As Long
    Dim strPublicKey As String
    
    ' We are given the following hex data
    strMessage = "d4 36 e9 95 69 fd 32 a7 c8 a0 5b bc 90 d3 2c 49"
    strSeed = "aa fd 12 f6 59 ca e6 34 89 b4 79 e5 07 6d de c2 f0 6c b5 8f"
    ' --in practice, we'd be generating a new random seed each time
    ' --i.e.
    ' ReDim abSeed(19)
    ' Call RNG_Bytes(abSeed(0), 20, "", 0)
    ' --see rsaes_oaep_Encrypt()
    
    ' Convert the given hex data into byte arrays.
    ' --note that spaces and other non-hex chars are ignored
    abMessage = cnvBytesFromHexStr(strMessage)
    abSeed = cnvBytesFromHexStr(strSeed)
    
    ' Compute Hash(L, the empty string) in byte array format
    ' --we know the resulting digest is going to be 20 bytes long
    nhLen = SHA1_BYTE_LEN
    ReDim abLHash(nhLen - 1)
    lngRet = HASH_Bytes(abLHash(0), nhLen, 0, 0, PKI_HASH_SHA1)
    Debug.Print "lHash: " & cnvHexStrFromBytes(abLHash)
    
    ' Compute lengths
    nmLen = BytesLength(abMessage)
    nkLen = 1024 / 8    ' We hard code in this example
    ndbLen = nkLen - 1 - nhLen
    npsLen = ndbLen - nmLen - nhLen - 1
    ' Catch error
    If npsLen < 0 Then
        MsgBox "Message is too long", vbCritical
        Exit Function
    End If
    
    ' Construct DB = lHash || Padding || M
    ReDim abDB(ndbLen - 1)
    iOffset = 0
    ' Copy lHash into DB
    For i = 0 To nhLen - 1
        abDB(iOffset) = abLHash(i)
        iOffset = iOffset + 1
    Next
    ' Followed by npsLen zero bytes
    ' --we don't have to do this as the bytes should already be zero
    For i = 0 To npsLen - 1
        abDB(iOffset) = 0
        iOffset = iOffset + 1
    Next
    ' Followed by a single 0x01 byte
    abDB(iOffset) = &H1
    iOffset = iOffset + 1
    ' And then the message itself
    For i = 0 To nmLen - 1
        abDB(iOffset) = abMessage(i)
        iOffset = iOffset + 1
    Next
    Debug.Print "DB: " & cnvHexStrFromBytes(abDB)
    
    ' Compute dbMask = MGF(seed, length(DB))
    abDbMask = MGF1(abSeed, ndbLen)
    Debug.Print "dbMask:   " & cnvHexStrFromBytes(abDbMask)
    
    ' Compute maskedDB = DB XOR dbMask
    abMaskedDB = XorBytes(abDB, abDbMask)
    Debug.Print "maskedDB: " & cnvHexStrFromBytes(abMaskedDB)
    
    ' Compute seedMask = MGF(maskedDB, length(seed))
    abSeedMask = MGF1(abMaskedDB, nhLen)
    Debug.Print "dbSeedMask: " & cnvHexStrFromBytes(abSeedMask)
    
    ' Compute maskedSeed = seed XOR seedMask
    abMaskedSeed = XorBytes(abSeed, abSeedMask)
    Debug.Print "maskedSeed: " & cnvHexStrFromBytes(abMaskedSeed)
    
    ' Build EM = 00 || maskedSeed || maskedDB
    ' --be careful, EM is different in the examples and in PKCS#1
    ' --in PKCS#1, EM does not include the leading zero.
    ReDim abBlock(0)
    abBlock(0) = 0
    abBlock = AppendBytes(abBlock, abMaskedSeed)
    abBlock = AppendBytes(abBlock, abMaskedDB)
    Debug.Print "EM: " & cnvHexStrFromBytes(abBlock)
    
    ' Encrypt using RSA public key
    ' Get key from the file
    ' --in practice we would have read this at the start and found out nkLen then
    ' --rather than hard-coding it in as we did above for this example
    strPublicKey = rsaReadPublicKey(TEST_KEY_PATH & "rsa1024pub.bin")
    If Len(strPublicKey) = 0 Then
        MsgBox "Cannot read public key", vbCritical
        Exit Function
    End If
    
    ' Check the length is OK
    If RSA_KeyBytes(strPublicKey) <> nkLen Then
        MsgBox "Key length does not match", vbCritical
        Exit Function
    End If
    
    ' Encrypt
    lngRet = RSA_RawPublic(abBlock(0), nkLen, strPublicKey, 0)
    Debug.Print "CT: " & cnvHexStrFromBytes(abBlock)

End Function

Public Function rsaes_oaep_decrypt_example()
' This decrypts the ciphertext from the previous example
    Dim lngRet As Long
    Dim abMessage() As Byte
    Dim abSeed() As Byte
    Dim abDB() As Byte
    Dim abLHash() As Byte
    Dim abPHash() As Byte
    Dim abDbMask() As Byte
    Dim abMaskedDB() As Byte
    Dim abSeedMask() As Byte
    Dim abMaskedSeed() As Byte
    Dim abBlock() As Byte
    Dim nhLen As Long   ' length of hash = 20 for SHA-1
    Dim nkLen As Long   ' length, k, of RSA key modulus (= 1024/8 = 128 here)
    Dim nmLen As Long   ' length of message, M
    Dim npsLen As Long  ' length of padding string, PS
    Dim ndbLen As Long  ' length of data block, DB
    Dim i As Long
    Dim iOffset As Long
    Dim strPrivateKey As String
    
    ' We are given the ciphertext in hex format, so convert to bytes
    abBlock = cnvBytesFromHexStr( _
"1253E04DC0A5397BB44A7AB87E9BF2A039A33D1E996FC82A94CCD30074C95DF763722017069E5268DA5D1C0B4F872CF653C11DF82314A67968DFEAE28DEF04BB6D84B1C31D654A1970E5783BD6EB96A024C2CA2F4A90FE9F2EF5C9C140E5BB48DA9536AD8700C84FC9130ADEA74E558D51A74DDF85D8B50DE96838D6063E0955")
    Debug.Print "CT: " & cnvHexStrFromBytes(abBlock)
    
    ' Read in the private key
    strPrivateKey = rsaReadPrivateKey(TEST_KEY_PATH & "rsa1024epk.bin", "password")
    If Len(strPrivateKey) = 0 Then
        MsgBox "Cannot read private key", vbCritical
        Exit Function
    End If
    
    nkLen = RSA_KeyBytes(strPrivateKey)
    Debug.Print "RSA key is " & nkLen & " bytes long (" & RSA_KeyBits(strPrivateKey) & ") bits"
    
    ' Decrypt
    lngRet = RSA_RawPrivate(abBlock(0), nkLen, strPrivateKey, 0)
    
    ' We are done with the private key, so erase it
    Call WIPE_String(strPrivateKey, Len(strPrivateKey))
    strPrivateKey = ""
    
    If lngRet <> 0 Then
        MsgBox "Decryption error", vbCritical
        Exit Function
    End If
    
    Debug.Print "EM: " & cnvHexStrFromBytes(abBlock)
    
    ' EM = 00 || maskedSeed || maskedDB
    ' so check leading byte is zero and then split up
    If abBlock(0) <> 0 Then
        MsgBox "Decryption error", vbCritical
        Exit Function
    End If
    
    ' Now we decode according to EME-OAEP-DECODE
    nhLen = SHA1_BYTE_LEN
    ndbLen = nkLen - nhLen - 1
    ReDim abMaskedSeed(nhLen - 1)
    ReDim abMaskedDB(ndbLen - 1)
    iOffset = 1
    For i = 0 To nhLen - 1
        abMaskedSeed(i) = abBlock(iOffset)
        iOffset = iOffset + 1
    Next
    Debug.Print "maskedSeed: " & cnvHexStrFromBytes(abMaskedSeed)
    For i = 0 To ndbLen - 1
        abMaskedDB(i) = abBlock(iOffset)
        iOffset = iOffset + 1
    Next
    Debug.Print "maskedDB:   " & cnvHexStrFromBytes(abMaskedDB)
    
    ' Let seedMask = MGF(maskedDB, hLen)
    abSeedMask = MGF1(abMaskedDB, nhLen)
    Debug.Print "seedMask: " & cnvHexStrFromBytes(abSeedMask)
    
    ' Let seed = maskedSeed \xor seedMask
    abSeed = XorBytes(abMaskedSeed, abSeedMask)
    Debug.Print "seed:     " & cnvHexStrFromBytes(abSeed)
    
    ' Let dbMask = MGF(seed, ||EM|| - hLen)
    abDbMask = MGF1(abSeed, ndbLen)
    Debug.Print "dbMask: " & cnvHexStrFromBytes(abDbMask)
    
    ' Let DB = maskedDB \xor dbMask
    abDB = XorBytes(abMaskedDB, abDbMask)
    Debug.Print "DB:     " & cnvHexStrFromBytes(abDB)
    
    ' Let pHash = Hash(P), an octet string of length hLen
    ' where P is the empty string in this implementation
    ReDim abPHash(nhLen - 1)
    lngRet = HASH_Bytes(abPHash(0), nhLen, 0, 0, PKI_HASH_SHA1)
    Debug.Print "pHash:  " & cnvHexStrFromBytes(abPHash)
    
    ' Separate DB = pHash' || PS || 01 || M
    ' If there is no 01 octet to separate PS from M, output "decoding error" and stop.
    ' If pHash' does not equal pHash, output "decoding error" and stop.
    ReDim abLHash(nhLen - 1)
    For i = 0 To nhLen - 1
        If abDB(i) <> abPHash(i) Then
            MsgBox "Decryption error", vbCritical
            Exit Function
        End If
    Next
    
    ' Work through the zero bytes of PS, if any, to find the 01 byte
    For i = nhLen To ndbLen - 1
        If abDB(i) <> 0 Then
            Exit For
        End If
    Next
    If i >= ndbLen Then ' Too long - no message at all
        MsgBox "Decryption error", vbCritical
        Exit Function
    End If
    If abDB(i) <> &H1 Then
        MsgBox "Decryption error", vbCritical
        Exit Function
    End If
    ' The remainder of DB is M, the message
    iOffset = i + 1
    nmLen = ndbLen - iOffset
    Debug.Print "M is " & nmLen & " bytes long"
    ReDim abMessage(nmLen - 1)
    For i = 0 To nmLen - 1
        abMessage(i) = abDB(iOffset + i)
    Next
    
    ' Output M.
    Debug.Print "M:  " & cnvHexStrFromBytes(abMessage)
    
End Function

'******************************************************
' HOW TO ENCRYPT AN UNENCRYPTED PRIVATE KEY INFO FILE *
'******************************************************
Public Function encryptPrivateKeyInfo()
' Converts the unencrypted pkcs-8 PrivateKeyInfo data file into encrypted pkcs-8 format
    Dim strPrivateKey64 As String
    Dim strPRIFile As String
    Dim strEPKFile As String
    Dim strPassword As String
    Dim nKeyLen As Long
    Dim lngRet As Long
    
    strPRIFile = TEST_KEY_PATH & "rsa1024pri.bin"
    strEPKFile = TEST_KEY_PATH & "rsa1024epk.bin"
    strPassword = "password"
    
    ' First read in as a string from the unencrypted file
    ' How long is PrivateKey string?
    nKeyLen = RSA_ReadPrivateKeyInfo("", 0, strPRIFile, 0)
    If nKeyLen <= 0 Then
        MsgBox "Unable to read private key info", vbCritical
        Exit Function
    End If
    Debug.Print "Private key string is " & nKeyLen & " characters long"
    ' Pre-dimension the string to receive data
    strPrivateKey64 = String(nKeyLen, " ")
    ' Read it in
    lngRet = RSA_ReadPrivateKeyInfo(strPrivateKey64, nKeyLen, strPRIFile, 0)
    
    ' Then save in encrypted form
    lngRet = RSA_SaveEncPrivateKey(strEPKFile, strPrivateKey64, 1000, strPassword, 0)
    
End Function