Share this page 

Use Microsoft Crypto APITag(s): WinAPI/Registry


With almost all Windows installation, the Microsoft Crypto API is available.

CryptoAPI 1.0 is provided through Microsoft Windows NT 4.0 and Microsoft Internet Explorer 3.0 and later. CryptoAPI 1.0 will also ship with the Windows 95 update.

Microsoft provides a separate COM object to make it easy to exploit this API from VBScript or Powerbuilder. But you need to installed the COM object before using it. This How-to will show you how to call directly the Crypto DLL.

The n_cst_crypto object can encrypt/decrypt a string based on a given key. This can be used to encrypt user/password entries in INI file for example.

Based on this Visual Basic example, the PB7 PBL containing the n_cst_crypto object can be download from here.

Many thanks to Martyn Bannister for VB to PB development.

To encrypt a string

n_cst_crypto lnv_crypt
string ls_encrypted

lnv_crypt = CREATE n_cst_crypto
ls_encrypted = lnv_crypt.EncryptData("my sensitive data" , "SecretKey")
DESTROY lnv_crypt

To decrypt a string

n_cst_crypto lnv_crypt
string ls_decrypted

lnv_crypt = CREATE n_cst_crypto
ls_decrypted = lnv_crypt.DecryptData(is_crypted , "SecretKey")
DESTROY lnv_crypt

Check this How-to for a powerscript-only.


J.C. de Souza Ribeiro wrote :
I tried to use your "PB7 PBL containing the n_cst_crypto object" to encrypt
a string of 59 numeric digits, using the MD5 algorithm, but everytime I run
it, using different values, I get strings of different lengths and I was
expecting always a string of 32 bytes.
My answer was
The original VB algorithm is making a great deal to make sure that there
is no tab/cr/lf characters in the result string and do encryption (with
a counter) again if there are present. I suspect this is why the length
is varying.
After some research, he sent me a function using the same API to solve his problem. The main idea to convert the result into hexadecimal format to eliminate the control characters.
/*
 External Function Definitions
  FUNCTION Boolean CryptAcquireContextA (ref ulong hProv, &
      ref string pszContainer, &
      ref string pszProvider, ulong dwProvType, &
      ulong dwFlags) &
    LIBRARY "advapi32.dll"

 FUNCTION Boolean CryptReleaseContext (ulong hProv, ulong dwFlags) &
    LIBRARY "advapi32.dll"

 FUNCTION Boolean CryptCreateHash (ulong hProv, uint Algid, ulong hKey, &
      ulong dwFlags, ref ulong phHash) &
    LIBRARY "advapi32.dll"

 FUNCTION Boolean CryptHashData (ulong hHash,  ref string pbData, &
      ulong dwDataLen, ulong dwFlags) &
    LIBRARY "advapi32.dll"

 FUNCTION Boolean CryptDestroyHash (ulong hHash) &
    LIBRARY "advapi32.dll"

 FUNCTION Boolean CryptGetHashParam (ulong hHash, ulong dwParam, &
     ref blob pbData, &
     ref ulong pdwDataLen, ulong dwFlags) &
    LIBRARY "advapi32.dll"

 FUNCTION Ulong GetLastError () Library "kernel32.dll"
*/

// Constants
CONSTANT ULONG PROV_RSA_FULL = 1
CONSTANT ULONG CRYPT_VERIFYCONTEXT  =  4026531840 // 0xF0000000
CONSTANT ULONG CALG_MD5 = 32771 // 4<<13 | 0 | 3
CONSTANT ULONG HP_HASHVAL = 2 //  0x0002


public function string of_md5 (string as_text);
  // Calculate the MD5 message digest hash of a string
  // Using the Windows Crypto API

  ulong MD5LEN = 16
  ulong hProv // provider handle
  ulong hHash // hash object handle
  ulong err_number
  String s_result, s_null
  Integer i, l, r, b
  Blob{16} bl_hash
  Blob{1} bl_byte

  SetNull (s_null)
  ulong cbHash = 0
  CHAR HexDigits[0 TO 15] = &
    {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}

  //Get handle to the crypto provider
  IF NOT CryptAcquireContextA&
     (hProv, s_null, s_null, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) &
     THEN
     err_number = GetLastError()
     return 'acquire context failed ' + String (err_number)
  END IF

  // Create the hash object
  IF NOT CryptCreateHash(hProv, CALG_MD5, 0, 0, hHash) THEN
     err_number = GetLastError()
     CryptReleaseContext(hProv, 0)
     return 'create hash failed ' + String (err_number)
  END IF

  // Add the input to the hash
  IF NOT CryptHashData(hHash, as_text, Len(as_text), 0) THEN
     err_number = GetLastError()
     CryptDestroyHash(hHash)
     CryptReleaseContext(hProv, 0)
     return 'hashdata failed ' + String (err_number)
  END IF

  // Get the hash value and convert it to readable characters
  cbHash = MD5LEN
  IF (CryptGetHashParam(hHash, HP_HASHVAL, bl_hash, cbHash, 0)) THEN
     FOR i = 1 TO 16
       bl_byte = BlobMid (bl_hash, i, 1)
       b = Asc (String(bl_byte))
       r = Mod (b, 16) // right 4 bits
       l = b / 16      // left 4 bits
       s_result += HexDigits [l] + HexDigits [r]
     NEXT
  ELSE
     err_number = GetLastError()
     return 'gethashparam failed ' + String (err_number)
  END IF

  // clean up and return
  CryptDestroyHash(hHash)
  CryptReleaseContext(hProv, 0)

  return s_result

Since PB10 is Unicode, you need to specify to the String() function to use ANSI.
[PB7/8/9]
       b = Asc (String(bl_byte))
[PB10]
       b = AscA(String(bl_byte,EncodingANSI!))
thanks to ramo76 for the PB10 AscA bug fix
Another fix for PB10.5 from Thomas Mathys.
I took your code to encode password by MD5 in a powerbuilder 10.5 application,
but I saw the result was different than what I obtained with others tools
(Oracle, Javascript example,...), if I encoded more than one character.

I searched a lot, and finally found a solution: you must pass your text as a
blob, and not as a string, to the CryptHashData function. You got to change
the prototype and your powerscript must be changed like this:
...
  // Add the input to the hash
  Blob bl_text
  bl_text = Blob(as_text, EncodingANSI!)
  IF NOT CryptHashData(hHash, bl_text, Len(bl_text), 0) THEN
...

A complete Powerbuilder object is available here : http://www.topwizprogramming.com/freecode_crypto.html
If you want to generate SHA1 hash then your DBMS may help you.

For Oracle 10g, it's something like :

SELECT DBMS_CRYPTO.HASH (utl_raw.cast_to_raw('Tester12'), :sh1) FROM dual;
For MS SQL Server, it's the HashBytes function, see http://msdn.microsoft.com/en-us/library/ms174415.aspx