API routine CryptEncrypt() returns error 234

API routine CryptEncrypt() returns error 234

Hello All,

We are trying to use the CryptEncrypt() API routine to encode a text
string. We have this working in a Visual Basic program and are now
trying to port it to a Fortran program.

In our program we call the CryptEncrypt() API routine twice. The first
time we want to send a null or blank string along with the current clear
text string length to get back the encoded string length. We can then
check if our output string will be long enough to contain the encoded
string.

When we call the CryptEncrypt() routine the first time in the Fortran
program we get an error code of 234. That error translates to "More
data is available" or ERROR_MORE_DATA. I'm not sure how to interpret
that error code.
I think we have a problem with defining the null string to pass in; we
have tried several options including the pointer to a blank string,
NULL, a zero "0" that all return the same error code 234.

I appreciate your comments and advice on how we can use the
CryptEncrypt() routine to encode a text string from a Fortran program.

Thanks for your help!

Regards,

Greg Thorwald
Structural Reliability Technology
1898 South Flatiron Court, Suite 235
Boulder, Colorado 80301
voice: (303) 415-1475 FAX: (303) 415-1847
e-mail: greg_thorwald@srt-boulder.com

10 posts / 0 new
Last post
For more complete information about compiler optimizations, see our Optimization Notice.

The documentation for CryptEncrypt says:

If the buffer allocated for pbData is not large enough to hold the data, GetLastError returns ERROR_MORE_DATA and stores the required buffer size, in bytes, in the DWORD value pointed to by pdwDataLen.

which is what I suggested to you earlier. Have you looked into that?

Steve

Retired 12/31/2016

I decided to try my hand at translating the MSDN sample into Fortran... I'll let you know how it works out.

Steve

Retired 12/31/2016

Hello,

Steve L., thanks for your comments, and I hope you have success with your test!

We have been checking and looking at the buffer size being sent in to the CryptEncrypt() PI routine. The PI routine call statement in our Fortran program looks like:

Result = CryptEncrypt(Key, 0, 1, 0, LAC(string_name), LAC(lData), lBuffer)

Where lResult is a boolean to let us know about successful completion, either 0 for an error or 1 for success. Then lKey is the previously obtained handle to the encryption key; 0, 1, 0 are default values or flags; the LOC(string_name) is the pointer to the string variable; LOC(lData) is the pointer to the length of the characters to be encrypted in string_name (the first lData characters); and finally lBuffer is the size of the buffer that can be used (the total size of the string); that is if I understand the parameters correctly. Since CryptEncrypt() will want to change the string and the length of the string after encryption it makes sense that pointers would be used for the in/out parameters so that the values of string_name and lData can be updated.

We have tried several combinations of string size and buffer size. For example we declare the string to be large, say LEN=100 and then pass in lData = 5; we think this indicates we want the first 5 characters in the string to be encrypted and there is a total string buffer size of 100 that can be used for the encrypted string.

In each case we notice that on return from CryptEncrypt() the lData value is unchanged from its initial value. In the above example, lData = 5 before the call and then again after the call. That is a suprise since I think that the encrypted string should be a little longer; I would expect lData to be something like 8 to indicate the new encrypted string length within the total buffer available of 100 in string_name.

We have this working in a Visual Basic program. In that VB program we call CryptEncrypt() once with lData=5 to find out how long the encrypted string will be. We pass in the vbNullString in this first call since we are not yet encrypting the string; then CryptEncrypt() updates lData to be 8 which is the length of the encrypted string. We then make sure that the string variable is large enough to contain the 8 characters of the encrypted string; we check that 8 < lBuffer = 100. In the second call to CryptEncrypt() we send the 5 character string (lData=5) and set the buffer size to 8 (lBuffer=8). That works nicely in the VB program.

Now that we are trying to port that VB program over to Fortran, we may be having some problems with the string buffer size or maybe the pointer to the string or the pointer to the string size (lData).

Could it be that when we use LOC(lData) as the pointer to the string length in the call statement we don't see the changed string length value? I had thought that any changes made to lData in the routine would be apparent after return from the CryptEncrypt() routine since lData has been passed by reference. Maybe I need to do the pointer to the string and the string length, lData, in a different way.

We also tried making the available buffer very large; we set lData=5 and lBuffer=100; that should be enough room for the 8 encrypted characters. It is at this point we get error 234 (using the GetLastError() API routine). As indicated in previous replies, that can mean the buffer size is not large enough to
contain the encrypted string. It seems that lBuffer=100 characters would be plenty of room for the encrytped string (expecting 8 characters). Again, maybe we are having trouble with the pointers to the string or the string length.

Another issue is that the API routine is likely expecting a null terminated string. We have been adding the null termination using the CHAR(0) value as:

k = lData + 1 ! get the character location just past the string length
string_name(k:k) = CHAR(0) ! put the null string terminator for API

I had thought

Hi,

Sorry, I don't have much time to look at this, but two things jump out at me.

Look at the interface to CryptEcrypt in advapi32.f90. First notice that the pbData and pdwDataLen parameters both have the REFERENCE attribute, so you should not be using LOC().

Then note that the pbData element has been mapped to an integer type in Fortran. This needs to be dealt with. Create and use a new interface block to CryptEncrypt (or modify the one in advapi32). If you feel like trying different things, you could try just adding the NO_ARG_CHECK attribute to the pbData arg and see if that does the trick. Or just resort to removing the REFERENCE attribute on pbData and then use loc(string_name).

hth,
John

An easier way to deal with pbData is to just pass %REF(variable), whatever the type of the variable is. In 6.5 and later, that will override type checking.

Steve

Retired 12/31/2016

Ok, I seem to have the Encrypt side of the sample working. It was a straightforward translation of the MSDN sample. I suspect the problem you're having with ERROR_MORE_DATA is that the encryption can create more data than you're encrypting, so you need to have a buffer that's larger than the data block, plus some if it's a block cipher.

Here's the relevant part of my code:

! Determine number of bytes to encrypt at a time.  This must be a
! multiple of ENCRYPT_BLOCK_SIZE.  ENCRYPT_BLOCK_SIZE is set
! as a PARAMETER constant.
!
dwBlockLen = 1000 - MOD(1000,ENCRYPT_BLOCK_SIZE)

! Determine the block size.  If a block cipher is used,
! it must have room for an extra block.
!
if (ENCRYPT_BLOCK_SIZE > 1) then
  dwBufferLen = dwBlockLen + ENCRYPT_BLOCK_SIZE
else 
  dwBufferLen = dwBlockLen
  end if

! Allocate memory  pbBuffer is a POINTER to array Buffer
!
pbBuffer = malloc(dwBufferLen)
if (pbBuffer == 0) then
  write (*,*) "Out of memory!" 
  return
  end if
write (*,*) "Memory has been allocated for the buffer." 

! In a DO loop, read from the source file, encrypt the
! data, and write to the destination file.
!
Final = FALSE
do while (BytesRemaining > 0)

  ! Read up to dwBlockLen bytes from the source file.
  !
  dwCount = min(BytesRemaining, dwBlockLen)
  BytesRemaining = BytesRemaining - dwCount
  if (BytesRemaining == 0) Final = TRUE
  read (1,iostat=ret) buffer(1:dwCount)
  if (ret /= 0) then
    write (*,*) "Error ",ret," reading plaintext!" 
	return
	end if

  ! Encrypt data
  !
  ret = CryptEncrypt (&
     hKey, &
     0, &
     Final, &
     0, &
     %ref(Buffer), &
     dwCount, &
     dwBufferLen)
  if (ret == FALSE) then
    call handle_error ("Error during CryptEncrypt!")
	return
	end if

  ! Write encrypted data to the destination file
  ! Note that dwCount is modified by CryptEncrypt
  ! and may be larger than the amount of data encrypted.
  !
  write (2,iostat=ret) buffer(1:dwCount)
  if (ret /= 0) then
    write (*,*) "Error ",ret," writing ciphertext!" 
	return
	end if

  end do

When I have the whole thing done, I'll post a pointer to it.

Steve

Retired 12/31/2016

Hello,

The advice to use %REF(variable) helped a great deal! Taking the easy way,
I have used the %REF(variable) in the call to the CryptEncrypt() API routine,
and it now works as expected. Our call looks like:

lResult = CryptEncrypt(lKey, 0, 1, 0, %REF(sText_Encrypt), %REF(lClear), lData)

Where sText_Encrypt is the clear text going in and the encrypted text on return,
lClear is the number of characters to encrypt, and lData is the available size
in the string to put the encrypted text.

And then for decryption we are using:

CryptDecrypt(lKey, 0, 1, 0, %REF(sText_Decrypt), %REF(lData))

Here sText_Decrypt is the encrypted text going in and the clear text coming out,
and lData is the length of the clear text (probably smaller than the length of
the encrypted string); I notice that there can be some "junk" at the end of the
sText_Decrypt string, just be sure to only look at the first lData characters
for the clear text.

I think that the confusion for me came from the description in the interface
statement in the ADVAPI32.f90 file; there the pbData argument is passed by
reference as specified by !DEC$ATTRIBUTES REFERENCE, but declared with the
integer(BYTE) statement (see previous replies).
This sent my thought process toward wanting to use pointers for the string
and the string length (pbData and pdwDataLen). I will have to be more vigilant
when determining the variable types and declarations for API routines.

Thanks to Steve L. and John T. for their great advice!

Greg T.

Glad to hear you have got it working. I have the corresponding decrypt sample working too (it's almost a duplicate of the encrypt sample - took me all of three minutes to make the edits.)

I'll polish it up - adding the no-password "blob" code and prompting for arguments, then submit it as a sample for future CVF kits. I'll also put it on an FTP server and post a pointer.

Steve

Retired 12/31/2016

Here you go! (If it doesn't seem to be there, try again later.) Comments welcome. I wasn't able to test the no-password paths, since I'm not sure how to load a public key for it to retrieve.

Steve

[Edit - the link in this post is now broken some 14 years later, but I turned this into the Crypto sample that is included in the Win32.zip sample collection in the Intel Visual Fortran installation.]

Retired 12/31/2016

Leave a Comment

Please sign in to add a comment. Not a member? Join today