Hello everyone,
I'm trying to use sas/secure to generate a hmac-sha1 oauth signature. I have SAS 9.3 installed on a PC running windows 7 professional. Note I tried using proc pwencode with Method=sas003. My code ran, but the signature did not match the one I generated online. It was way too long. I'm using the linked page below to create the oauth_signature. How should I go about solving this problem? Do I need to use java (yikes!), or can I simply use a proc or options statement? Any help would be greatly appreciated. Thanks very much.
-Bill
I would recommend you continue using the private Java runtime provided by your SAS installation.
This is because they are looking for the base64 encoding of the byte array, you are creating the base64 encoding of the hexadecimal representation of the byte Hmac-SHA1 digest.
%let key = kd94hf93k423kf44&pfkkdhi9sl3r4s00;
%let message = GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal;
proc groovy;
add sasjar="commons_codec" version="1.7.0.0_SAS_20121211183158";*version may differ based on your installation. Check your SAS Versioned Jar Repository;
submit "&key." "&message.";
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import org.apache.commons.codec.binary.Base64
def base64hmacsha1(key, message) {
mac = Mac.getInstance("HmacSHA1")
mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA1"))
sha1_bytes = mac.doFinal(message.getBytes())
base64 = new Base64()
return new String(base64.encode(sha1_bytes))
}
exports.base64hmacsha1 = base64hmacsha1(args[0], args[1])
endsubmit;
quit;
%put &base64hmacsha1.;
/*tR3+Ty81lMeYAr/Fid0kMTYa/WM=*/
Message was edited by: FriedEgg - typo corrected in RED, also added reference for common-codec sasjar
Specifying method=sas003 will give you 256-bit key plus 16-bit salt AES encoding, totally different from SHA1. There is not a way (that I am familiar with) to generate a SHA1 hash with PROC PWENCODE. You can use java, which wouldn't be too complicated. Made much easier by the fact that PROC GROOVY exists in 9.3+
%let toHash=something to hashify;
proc groovy;
submit "&toHash.";
import java.security.MessageDigest
exports.sha1 = new BigInteger(1, MessageDigest.getInstance("SHA1").digest(args[0].getBytes())).toString(16)
endsubmit;
quit;
%put &sha1.;
Side-note: You shouldn't post the same question to multiple communities.
FriedEgg,
Thanks so much for your help. Code works and produces a sha1 signature. Quick follow-up questions, if I have key and text components to go into the digest function (digest = HMAC-SHA1 (key, text)), can I simply use a macro variable like %let disgest_inputs=&key.,&text.;? Or should I do something different?
My apologies about the double post. I deleted the other thread. I was having trouble posting my question the other night and inadvertently created a duplicate post.
Thanks again!
Regards,
Bill
%let key = mykey;
%let message = helloworld;
proc groovy;
submit "&key." "&message.";
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
def hmacsha1(key, message) {
mac = Mac.getInstance("HmacSHA1")
mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA1"))
sha1_bytes = mac.doFinal(message.getBytes())
return new BigInteger(1, sha1_bytes).toString(16)
}
exports.hmacsha1 = hmacsha1(args[0], args[1])
endsubmit;
quit;
%put &hmacsha1.;
FriedEgg,
Thanks so much for the code and your time. I tried to recreate the oauth_signature for a GET example on nouncer.com (url is below). I created macro variables that generate the key and message inputs correctly (comparisons are below). However, when I compute the oauth_signature, I get a much different answer to what's on the website. Noucner.com states that the oauth_signature must be base64-encoded. I'm using put statement with format $base64x32767. Is this correct? Also, should I upgrade java? Currently, I'm running version 6 update 24.
Thanks again and sorry to keep hassling you with this.
-Bill
Compare oauth signature inputs
Key
sas
kd94hf93k423kf44&pfkkdhi9sl3r4s00
website
kd94hf93k423kf44&pfkkdhi9sl3r4s00
Message
sas
GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal
website (called signature base string)
GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal
Compare oauth_signature output
sas
YjUxZGZlNGYyZjM1OTRjNzk4MDJiZmM1ODlkZDI0MzEzNjFhZmQ2Mw==
website
tR3+Ty81lMeYAr/Fid0kMTYa/WM=
Link to GET example on http://nouncer.com/oauth/authentication.html
(May have to first click on a radio button on the top of the page to something other than the example and then back on the example to see the details. This is the only way that I can view all the information.)
SAS log:
529 dm 'clear log';
530
531 *Oauth vars;
532 %let oauth_consumer_key=dpf43f3p2l4k3l03;
533 %let oauth_consumer_secret=kd94hf93k423kf44;
534 %let oauth_token=nnch734d00sl2jdk;
535 %let oauth_token_secret=pfkkdhi9sl3r4s00;
536 %let oauth_signature_method=HMAC-SHA1;
537 %let oauth_version=1.0;
538 %let url=%sysfunc(urlencode(http://photos.example.net/photos));
539 %let oauth_size=original;
540 %let oauth_file=vacation.jpg;
541 %let oauth_timestamp=1191242096;
542 %let oauth_nonce=kllo9940pd9333jh;
543
544 *Vars for oauth signature;
545 %let API_TIME_STAMP=%str(oauth_timestamp=&oauth_timestamp.);
546 %let API_NONCE=%str(oauth_nonce=&oauth_nonce.);
547 %let API_KEY=%str(oauth_consumer_key=&oauth_consumer_key.);
548 %let API_SECRET=%str(oauth_consumer_secret=&oauth_consumer_secret.);
549 %let API_METHOD=%str(oauth_signature_method=&oauth_signature_method.);
550 %let API_TOKEN=%str(oauth_token=&oauth_token.);
551 %let API_VERSION=%str(oauth_version=1.0);
552 %let API_SIZE=%str(size=&oauth_size.);
553 %LET API_FILE=%str(file=&oauth_file.);
554 %let API_CONSUMER_SECRET=%sysfunc(urlencode(&oauth_consumer_secret.));
555 %let API_TOKEN_SECRET=%sysfunc(urlencode(&oauth_token_secret.));
556
557 %let
557! string=&API_FILE.%str(&)&API_KEY.%str(&)&API_NONCE.%str(&)&API_METHOD.%str(&)&API_TIME_STAMP.%str
557! (&)&API_TOKEN.%str(&)&API_VERSION.%str(&)&API_SIZE.;
558 %let message=%str(GET)%str(&)&url.%str(&)%sysfunc(urlencode(&string.));
559 %put &message.;
GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-
SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal
560
561 %let key=&API_CONSUMER_SECRET.%str(&)&API_TOKEN_SECRET.;
562 %put &key;
kd94hf93k423kf44&pfkkdhi9sl3r4s00
563
564
565 proc
565! groovy;
566
567 submit "&key." "&message.";
WARNING: The quoted string currently being processed has become more than 262 characters long. You
might have unbalanced quotation marks.
568
569 import javax.crypto.Mac
570 import javax.crypto.spec.SecretKeySpec
571
572 def hmacsha1(key, message) {
573 mac = Mac.getInstance("HmacSHA1")
574 mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA1"))
575
576 sha1_bytes = mac.doFinal(message.getBytes())
577
578 return new BigInteger(1, sha1_bytes).toString(16)
579 }
580
581 exports.hmacsha1 = hmacsha1(args[0], args[1])
582
583 endsubmit;
NOTE: Exporting macro variable "hmacsha1".
NOTE: The SUBMIT command completed.
584
585 quit;
NOTE: PROCEDURE GROOVY used (Total process time):
real time 0.01 seconds
cpu time 0.00 seconds
586
587 %put &hmacsha1.;
b51dfe4f2f3594c79802bfc589dd2431361afd63
588
589 data _null_;
590 oauth_signature = put("&hmacsha1.",$base64x32767.);
591 call symput('oauth_signature',oauth_signature);
592 run;
NOTE: DATA statement used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
593
594 %put &oauth_signature.;
YjUxZGZlNGYyZjM1OTRjNzk4MDJiZmM1ODlkZDI0MzEzNjFhZmQ2Mw==
I would recommend you continue using the private Java runtime provided by your SAS installation.
This is because they are looking for the base64 encoding of the byte array, you are creating the base64 encoding of the hexadecimal representation of the byte Hmac-SHA1 digest.
%let key = kd94hf93k423kf44&pfkkdhi9sl3r4s00;
%let message = GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal;
proc groovy;
add sasjar="commons_codec" version="1.7.0.0_SAS_20121211183158";*version may differ based on your installation. Check your SAS Versioned Jar Repository;
submit "&key." "&message.";
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import org.apache.commons.codec.binary.Base64
def base64hmacsha1(key, message) {
mac = Mac.getInstance("HmacSHA1")
mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA1"))
sha1_bytes = mac.doFinal(message.getBytes())
base64 = new Base64()
return new String(base64.encode(sha1_bytes))
}
exports.base64hmacsha1 = base64hmacsha1(args[0], args[1])
endsubmit;
quit;
%put &base64hmacsha1.;
/*tR3+Ty81lMeYAr/Fid0kMTYa/WM=*/
Message was edited by: FriedEgg - typo corrected in RED, also added reference for common-codec sasjar
I added the classpath for the groovy-all.2.4.0.jar per your recommendation in another thread. However, now I'm getting a different type of error:
334 dm 'clear log';
335
336 %let key = %nrstr(kd94hf93k423kf44&pfkkdhi9sl3r4s00);
337 %let message =
337! %nrstr(GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddp
337! f43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_time
337! stamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal);
338
339
340 proc
340! groovy;
341
342 add classpath="C:\Program Files (x86)\Groovy\Groovy-2.4.0\lib\groovy-all.2.4.0.jar";
NOTE: The ADD CLASSPATH command completed.
343
344 submit "&key." "&message.";
WARNING: The quoted string currently being processed has become more than 262 characters long. You
might have unbalanced quotation marks.
345
346 import javax.crypto.Mac
347 import javax.crypto.spec.SecretKeySpec
348 import org.apache.commons.codec.binary.Base64
349
350 def base64hmacsha1(key, message) {
351 mac = Mac.getInstance("HmacSHA1")
352 mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA1"))
353
354 sha1_bytes = mac.doFinal(message.getBytes())
355
356 base64 = new Base64()
357 return new String(base64.encode(sha1_bytes))
358 }
359
360
361 exports.base64hmacsha1 = hmacsha1(args[0], args[1])
362
363
364 endsubmit;
groovy.lang.MissingMethodException: No signature of method: Script10.hmacsha1() is applicable for
argument types: (java.lang.String, java.lang.String) values: [kd94hf93k423kf44&pfkkdhi9sl3r4s00,
GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l
03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D119124209
6%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal]
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:54)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:78)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:44)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:143)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:155)
at Script10.run(Script10.groovy:17)
ERROR: The SUBMIT command failed.
365
366
367 quit;
NOTE: The SAS System stopped processing this step because of errors.
NOTE: PROCEDURE GROOVY used (Total process time):
real time 0.03 seconds
cpu time 0.01 seconds
WARNING: Apparent symbolic reference HMACSHA1 not resolved.
368 %put &hmacsha1.;
&hmacsha1.
369 /*tR3+Ty81lMeYAr/Fid0kMTYa/WM=*/
I did not test that posting and missed replacing the method call with the proper name. You should be good now.
Success! Thanks so very much for your time and help with this. I've tried numerous different ways to generate the oauth_signature. Finally, I found a solution that works with sas.
Regards,
Bill
@BillJones,
Glad it work out for you.
HI,
Firstly apologies if this is the wrong place to post...first time I've used this!
Thank you for posting the code above - it's really helped me. I'm trying to SHA1 encode entries in a file, but I can't find a way of incorporating the code into a macro and thus can only do one at a time using the code you provided below.
proc sql noprint;
select mskey, emailcode
into :mskey, :emailcode
from trust;
quit;
%let toHash=&mskey&emailcode;
proc groovy;
submit "&toHash.";
import java.security.MessageDigest
exports.sha1 = new BigInteger(1, MessageDigest.getInstance("SHA1").digest(args[0].getBytes())).toString(16)
endsubmit;
quit;
%put &toHash;
%put &sha1.;
When I try and incorporate in a macro it mentions that the submit block cannot be directly placed in a macro. Instead, place the submit block into a file first and then use %include to include the file within a macro definition. Sorry, my coding is not good enough to make this work
Any help would be much appreciated!
@NG_Forest,
It would be best to create a new post for you question as SHA1 and HMAC-SHA1 are significantly different topics.
Also, I have posted on this subject previously, it does not use PROC GROOVY, as it did not exist at the time (the code in this post could be adapted to use is though):
Using a macro to perform this type of task on every record in a file is really less than ideal. The better approach would be using the data step java object.
@FriedEgg,
Thanks for taking the time to respond and apologies for not starting a new post.
Once I have a Java compiler installed on my machine I'll certainly give your other method a go.
@NG_Forest
I updated the post to include a similar method using PROC GROOVY at the same link as above
@FriedEgg,
Once again, thank you so much...it works perfectly
Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 25. Read more here about why you should contribute and what is in it for you!
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.