Dcl-s toSign varchar(4096) Ccsid(*utf8);
Dcl-s oneTimeSecret varchar(128) Ccsid(*utf8);
Dcl-s KEY varchar(128) Ccsid(*utf8);
Dcl-s kdate Char(32) Ccsid(*hex);
Dcl-s kRegion Char(32) Ccsid(*hex);
Dcl-s kService Char(32) Ccsid(*hex);
Dcl-s kSigning Char(32) Ccsid(*hex);
KEY = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY';
oneTimeSecret = 'AWS4' + KEY;
toSign = '20120215';
kdate = HmacSHA256( toSign : oneTimeSecret );
toSign = 'us-east-1';
kRegion = HmacSHA256( toSign : kdate );
toSign = 'iam';
kService = HmacSHA256( toSign : kRegion );
toSign = 'aws4_request';
kSigning = HmacSHA256( toSign : kService );
kSigning will contain the "binary" data needed.
Now AMS wants that converted to a String representing the hex values.
Dcl-Pr Cvthexchar EXTPROC('cvthc');
Hexchars Char(2048) Options(*Varsize);
Chars Char(1024) Options(*Varsize);
Rtnlen Int(10) VALUE;
End-Pr;
Dcl-s OutString Char(64) ;
Clear OutString;
Cvthexchar( OutString : kSigning : 64 );
The signed value is 32 bytes long, so the hex string will be 64 long.
Note that outString is a CCSID 37 string and you would need to take that into account when you add it into the HTTP request.
Note that most strings are defined as Ccsid(*utf8) or Ccsid(*hex) based on how they are used.
The string input to the HMAC should be in UTF8.
Here is the sha256 procedure:
// --------------------------------------------------
// Procedure name: HmacSHA256
// Purpose: generate HMAC hash from input
// To account for CCSID conversions, this procedure assumes
// all input is already in the desired CCSID.
// Returns: HMAC Hash
// Parameter: message raw character stream
// Parameter: passphrase raw character stream
// --------------------------------------------------
Dcl-Proc HmacSHA256;
Dcl-Pi *N Char(32) Ccsid(*Hex);
message Varchar(32767) Const Ccsid(*Hex);
passphrase Varchar(128) Const Ccsid(*Hex);
End-pi;
Dcl-ds Qc3_Format_ALGD0500_T Qualified Template;
Hash_Alg Int(10);
End-ds;
Dcl-ds Qc3_Format_KEYD0200_T Qualified Template;
Key_Type Int(10);
Key_String_Len Int(10);
Key_Format Char(1);
Reserved1 Char(3);
Key_String Char(128) Ccsid(*hex);
End-ds;
Dcl-Ds Apierrords Qualified Template;
Bytespass Int(10) INZ(%SIZE(APIERRORDS));
Bytesavail Int(10) INZ(*ZERO);
MsgID Char(7) INZ(*BLANKS);
*N Char(1) INZ(X'00');
MsgDta Char(256) INZ(*BLANKS);
End-Ds;
// Qc3CalculateHMAC Caculate the HMAC.
// It is important to note that "datatohash" is CCSID(*HEX) so the system
// doesn't "Help" us by converting the data into CCSID 37 prior to the call.
// The Key String is also defined as CCSID(*HEX) so that it isn't converted either.
// This allows us to pass strings that have already been set to our desired ccsid.
Dcl-Pr GetHMAC ExtProc('Qc3CalculateHMAC');
datatohash Char(32767) const options(*varsize) ccsid(*hex);
pinDataLen Int(10) const;
pinFormat Char(8) const;
palgDesc Char(32767) OPTIONS(*VARSIZE) Const;
palgDescFmt Char(8) const;
pkeyDesc Char(32767) const options(*varsize);
pkeyDescFmt Char(8) const;
pcryptoProv Char(1) const;
pcryptoDev Char(10) const;
pHMAC Char(64) options(*varsize);
pErrorCode Char(32767) options(*varsize);
End-Pr;
// FROM:: /COPY QSYSINC/H,QC3CCI
Dcl-c Qc3_MD5 1 ;
Dcl-c Qc3_SHA1 2 ;
Dcl-c Qc3_SHA256 3 ;
Dcl-c Qc3_SHA384 4 ;
Dcl-c Qc3_SHA512 5 ;
Dcl-c Qc3_MD2 6 ;
Dcl-c Qc3_SHA224 7 ;
Dcl-c Qc3_SHA3_224 8 ;
Dcl-c Qc3_SHA3_256 9 ;
Dcl-c Qc3_SHA3_384 11 ;
Dcl-c Qc3_SHA3_512 12 ;
// Length of HMAC defined by algorithm.
// MD5 16 bytes
// SHA-1 20 bytes
// SHA-256 32 bytes
// SHA-384 48 bytes
// SHA-512 64 bytes
Dcl-ds alg likeds(Qc3_Format_ALGD0500_T);
Dcl-DS KeyFormat Likeds(Qc3_Format_KEYD0200_T);
Dcl-ds Errords Likeds(Apierrords);
Dcl-s Outhash char(32) Ccsid(*Hex);
OutHash = *Allx'00';
KeyFormat = *Allx'00';
KeyFormat.Key_Type = Qc3_SHA256;
KeyFormat.Key_String_Len = %Len(passphrase);
// The Minimum key length is defined based on the type of HASH.
// For Qc3_SHA256 the minimum is 32
If KeyFormat.Key_String_Len < 32;
KeyFormat.Key_String_Len = 32;
Endif;
KeyFormat.Key_Format= '0';
// This allows the key to be padded with NULs
KeyFormat.Key_String = *Allx'00';
%Subst( KeyFormat.Key_String : 1 : %Len(passphrase) ) = passphrase;
Errords = *AllX'00';
Errords.Bytespass = %Size(Errords);
alg.Hash_Alg = Qc3_SHA256;
Callp GetHMAC(
message
: %Len(message)
: 'DATA0100'
: alg
: 'ALGD0500'
: KeyFormat
: 'KEYD0200'
: '0'
: ''
: Outhash
: Errords );
Return OutHash;
End-proc;
Chris Hiebert
Senior Programmer/Analyst
Disclaimer: Any views or opinions presented are solely those of the author and do not necessarily represent those of the company.
From: RPG400-L <rpg400-l-bounces@xxxxxxxxxxxxxxxxxx> On Behalf Of Greg Wilburn
Sent: Friday, July 8, 2022 8:55 AM
To: RPG programming on IBM i <rpg400-l@xxxxxxxxxxxxxxxxxx>
Subject: RE: Signing AWS Requests with Signature Version 4
OK... so I'm almost all the way through the AWS example... I have the hashes calculated correctly with QC3CALCULATEHASH()
I'm having trouble with QC3CALCULATEHMAC() and getting the same result as in the Example.
https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
I have set both prototypes up to do the EBCDIC to ASCII conversion by specifying CCSID(819) on the prototype.
However, I'm having trouble understanding this section of the AWS documentation (specifically the "returns output in binary" part) of creating a signing key.
Do I need to convert each HMAC result to "binary" before the next call? If so, how can someone tell me how to do that?
-- from the webpage above ---
Use the digest (binary format) for the key derivation. Most languages have functions to compute either a binary format hash, commonly called a digest, or a hex-encoded hash, called a hexdigest. The key derivation requires that you use a binary-formatted digest.
The following example show the inputs to derive a signing key and the resulting output, where kSecret = wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY.
HMAC(HMAC(HMAC(HMAC("AWS4" + kSecret,"20150830"),"us-east-1"),"iam"),"aws4_request")
AWS signingKey should be:
c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9
My test code looks like this:
clientSecret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
signingKey =
CalculateHMAC(
CalculateHMAC(
CalculateHMAC(
CalculateHMAC('AWS4'+ clientSecret: '20150830'))
:'us-east-1' )
:'iam' )
:'aws4_request' );
I am not getting the same result.
As an Amazon Associate we earn from qualifying purchases.