How to call use .NET RijndaelManaged from native C

2019-01-27 00:50发布

问题:

Can anyone give example usage for using System.Security.Cryptography.RijndaelManaged from native Win32 using COM?


Here is a functional example of how to use .NET's System.Security.Cryptography.SHA256Managed

Note: This is an example of how to call SHA256Managed, and not RijndaelManaged**. i show this code to introduce some of the tricky concepts of calling COM objects with early and late-binding, and to show that calling .NET objects with COM Callable Wrappers is possible. Some people might throw up that hands when hearing i want to mix .NET and Win32 COM.

Note: The language is Delphi (which is what i wrote and tested it in), but transcoded to make it look slightly more like C# or Java, so the question is more accessible to a wider audience:

public static string HashStr256(String szPlaintext)
{
   OleVariant sha = CreateOleObject('System.Security.Cryptography.SHA256Managed');

   OleVariant inputBuffer = VarByteArrayOf(szPlaintext);

   Integer l = VarArrayLowBound(inputBuffer, 1);
   Integer h = VarArrayHighBound(inputBuffer, 1);

   ICryptoTransform crypto = IDispatch(sha) as ICryptoTransform;
   crypto.TransformFinalBlock(
         PSafeArray(TVarData(inputBuffer).VArray), //inputArray,
         l,  //input array offset
         h-l+1); //input array count

   OleVariant digest := sha.Hash;

   Result := ToBase64String(BytesOfVarByteArray(digest));
end;

With the helper function:

function VarByteArrayOf(const s: string): OleVariant;
var
    Data: Pointer;
begin
    //Create variant byte array to hold the bytes
    Result := VarArrayCreate([0, Length(s)-1], varByte);

    //Copy from Delphi array to Variant Array
    if Length(s) > 0 then
    begin
        Data := VarArrayLock(Result);
        try
            System.Move(s[1], Data^, Length(s));
        finally
            VarArrayUnlock(Result);
        end;
    end;
end;

The real reason i show code calling SHA256Managed is to demonstrate the difficulties i'm encountering when passing arrays to COM in Delphi. For example, if you simply relied on IDispatch late binding:

sha: OleVariant;
inputBuffer: OleVariant;
...
sha.TransformFinalBlock(inputBuffer, i, count);

Then at runtime you get The parameter is incorrect; something about IDispatch late binding doesn't support passing an OleVariant that is an array of bytes.

That's why i have to instead pass the safearray that is inside the OleVariant. As we all know a Variant is just a union structure, where we care about the SAFEARRAY member:

data: PSafeArray;

data := PSafeArray(TVarData(inputBuffer).VArray);
sha.TransformFinalBlock(data, i, count);

Except trying to pass a PSafeArray using IDispatch late-binding causes Delphi to throw-up:

Type not allowed in OLE Automation call

Which is why we're forced to partially abandon IDispatch late binding for ICryptoTransform early binding:

ICryptoTransform_Native = interface(IDispatch)
   ['{8ABAD867-F515-3CF6-BB62-5F0C88B3BB11}']
   ...
   function TransformFinalBlock(inputBuffer: PSafeArray; inputOffset: Integer; inputCount: Integer): PSafeArray; safecall;
   ...
end;

with

crypto: ICryptoTransform;
sha: OleVariant;
inputBuffer: OleVariant;

crypto = IDispatch(sha) as ICryptoTransform;
crypto.TransformFinalBlock(
         PSafeArray(TVarData(inputBuffer).VArray), //inputArray,
         i, n);

So that should be it, right? We've solved how to pass arrays to COM .NET objects? i should be all set to use Rijndael now; use early-binding with PSafeArray.

This is what we want to try with late-binding:

OleVariant aes := CreateOleObject('System.Security.Cryptography.RijndaelManaged');

OleVariant keyBytes = VarByteArrayOf(Key);
aes.Key := key;

that fails for the same reason above - you cannot passed OleVariant arrays to COM objects (The parameter is incorrect).

So we we should just use the early binding of SymmetricAlgorithm.Key and pass the Key as a PSafeArray:

var
   symmetric: ISymmetricAlgorithm;
   aes: OleVariant;
   keyBytes: OleVariant;
begin
   aes := CreateOleObject('System.Security.Cryptography.RijndaelManaged');

   symmetric:= (IDispatch(aes) as ISymmetricAlgorithm;
   symmetric.Key := PSafeArray(TVarData(inputBuffer).VArray);
   ...
end;

Except there is no ISymmetricAlgorithm interface; i made it up.

If you import the type library from mscorelib.tlb, which contains ICryptoTransform, there is no early binding ISymmetricAlgorithm. There is a late-bound ISymmetricAlgorithm:

IID__SymmetricAlgorithm: TGUID = '{05BC0E38-7136-3825-9E34-26C1CF2142C9}';
CLASS_SymmetricAlgorithm: TGUID = '{5B67EA6B-D85D-3F48-86D2-8581DB230C43}';
_SymmetricAlgorithm = interface;
SymmetricAlgorithm = _SymmetricAlgorithm;

_SymmetricAlgorithm = interface(IDispatch)
   ['{05BC0E38-7136-3825-9E34-26C1CF2142C9}']
end;
_SymmetricAlgorithmDisp = dispinterface
   ['{05BC0E38-7136-3825-9E34-26C1CF2142C9}']
end;

So how can i set the RijndaelManaged Key and IV from native Win32 COM? What late-binding syntax can i use?

How to call use .NET RijndaelManaged from native COM?

Further Attempts

Since there is no early-bound interface i can use, i have to focus on how to pass a byte array through IDispatch. The secret is what should be in the Variant that i pass to the COM object.

Since the early-bound version used safearrays, i'll start with that. First of all i will manually set OleVariant structure myself.

For each of the following attempts the following is true:

key: OleVariant;
data: PSafeArray;

data := PSafeArray(TVarData(keyBytes).VArray);

setting the variant enum myself.

  • Attempt 1: VT_ARRAY A SAFEARRAY pointer.

    TVarData(key).VType := VT_ARRAY;
    TVarData(key).VArray := data;
    
  • Attempt 2: VT_ARRAY A SAFEARRAY pointer.

    TVarData(key).VType := VT_ARRAY or VT_I1;
    TVarData(key).VArray := data;
    
  • Attempt 3: VT_SAFEARRAY A safe array. Use VT_ARRAY in VARIANT.

    TVarData(key).VType := VT_SAFEARRAY or VT_I1;
    TVarData(key).VArray := data;
    
  • Attempt 4: VT_SAFEARRAY A safe array. Use VT_ARRAY in VARIANT.

    TVarData(key).VType := VT_SAFEARRAY;
    TVarData(key).VArray := data;
    

All of them fail with a The parameter is incorrect error.

Get i get the sense that VT_ARRAY (a safearray) and VT_SAFEARRAY (a safearray) are just not what the COM Callable wrapper IDispatch interface is prepared to accept. Perhaps it needs to be VT_CARRAY.

Or maybe not. Maybe someone smarter than me can figure it out.

Bonus Reading

This is, essentially, a re-phrasing of a question i asked in 2008 on the Borland Newsgroups

Delphi compiler has built-in knowledge of COM SafeArrays?

  • .NET mshtml: How to pass a BSTR SAFEARRAY?