I am trying to encrypt 3 different elements into an authentication token for a http headers for an API. This API is built into Google Sheets, and I can't use anything else at the moment.
The authentication token requires 4 parts:
API Key
Timestamp in UTC format
API Action
API Secret Key
In the format of API KEY:TIMESTAMP:API ACTION:API Secret Key
For the purposes of this example, let's say that the
API key is test123,
UTC Date: Thu, 14 Apr 2011 22:44:22 GMT
API action is 'ledger'
API Secret key is UAV213Q
When I tested the example using following format "test123:Thu, 14 Apr 2011 22:44:22 GMT:ledger:UAV213Q" in python I got the result 15594d1f608134cbfa3075ecda4664519cd198738b8f5c3ffa2c95272b854199
This is the python script I used
def sha256():
# tested on Python 3.8.5
from urllib import parse, request
import hashlib
import datetime
from time import strftime, gmtime
# credentials and request params
my_merchant_id = 'apikey'
api_token = 'test123'
api_secret_key = 'UAV213Q'
my_timestamp = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
api_version = 2.9
action_verb = 'ledger'
# set up request params
data = parse.urlencode({'merchantId': my_merchant_id, 'token': api_token,
'version': api_version, 'action': action_verb})
# authentication
sig = api_token + ':' + my_timestamp + ':' + action_verb + ':' + api_secret_key
sig_hash = hashlib.sha256(sig.encode('utf-8')).hexdigest()
my_headers = {'x-ShareASale-Date': my_timestamp,
'x-ShareASale-Authentication': sig_hash}
print(sig_hash)
I've tried using solutions from the following other StackOverFlow questions
How do I get Google Apps Script to do SHA-256 encryption?, sha3-256 of a cell text in Google Spreadsheet, all the suggestions.
However, I keep getting the error message "This function is not allowed to reference a cell with NOW(), RAND(), or RANDBETWEEN()."
I have tried referencing a cell that is indirectly references the NOW() by having NOW() in A1 and having B1 =A1, I have even tried to convert it to text by using the TEXT().
The API key needs to have the timestamp to function. I was thinking about having that calculated in the App script itself, since it is a known constant. For example within the encryption script it would hardcode the api token, call the timestamp in utc format and hardcode the api secret key in the correct format and the maybe just the function to add the action so I can make change that so it would be sha256(ledger) and it would incorporate that into the encryption
How about this answer?
Modification points:
When I saw your python script, I confirmed that the specification shown in your question is different from that of the python script.
It seems that Thu, 14 Apr 2011 22:44:22 GMT is Thu, 14 Apr 2011 22:44:22 +0000
It seems that it is required to use "SHA_256" of the digest.
Sample script:
When your python script is converted to Google Apps Script, it becomes as follows. Please copy and paste it to the script editor and run the function myFunction at the script editor. By this, you can see the result value at the log.
function myFunction() {
const api_token = 'test123';
const api_secret_key = 'UAV213Q';
const my_timestamp = 'Thu, 14 Apr 2011 22:44:22 +0000';
const action_verb = 'ledger';
const value = `${api_token}:${my_timestamp}:${action_verb}:${api_secret_key}`;
const bytes = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, value);
const res = bytes.map(byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
console.log(res)
}
Result:
When test123:Thu, 14 Apr 2011 22:44:22 +0000:ledger:UAV213Q is used for above Google Apps Script and your python script, both results are the same as follows.
8c3a6873fe71c402dc1e3ca7bc828712e3dfb7a66ed09feeeca2152dd809df81
Reference:
computeDigest(algorithm, value)
Added:
Answer for additional question 1:
When you want to retrieve the date string like Thu, 14 Apr 2011 22:44:22 +0000, please use the following script.
const my_timestamp = new Date().toUTCString().replace("GMT", "+0000");
Answer for additional question 2:
When you want to retrieve the value as the upper case, please use the following script. But when I tested your python script, the result value is the lower case. So please be careful this.
function myFunction() {
const api_token = 'test123';
const api_secret_key = 'UAV213Q';
const my_timestamp = 'Thu, 14 Apr 2011 22:44:22 +0000';
const action_verb = 'ledger';
const value = `${api_token}:${my_timestamp}:${action_verb}:${api_secret_key}`;
const bytes = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, value);
const res = bytes.map(byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('').toUpperCase();
console.log(res)
}
In this case, 8C3A6873FE71C402DC1E3CA7BC828712E3DFB7A66ED09FEEECA2152DD809DF81 is obtained.
References:
toUTCString()
toUpperCase()
Related
I am trying to create an SAS Token to communicate with Azure API Management Rest API using JavaScript (Express.js). But using that actually leads me to a 401 Unauthorized. I am using the following lines of code.
// setting one day expiry time
const expiryDate = new Date(Date.now() + 1000 * 60 * 60 * 24)
const expiryString = expiryDate.toISOString()
const identifier = process.env.AZURE_APIM_IDENTIFIER
const key = process.env.AZURE_APIM_SECRET_KEY ?? ""
const stringToSign = `${identifier}\n${expiryString}`
const signature = CryptoJS.HmacSHA256(stringToSign, key)
const encodedSignature = CryptoJS.enc.Base64.stringify(signature)
// SAS Token
const sasToken = `SharedAccessSignature uid=${identifier}&ex=${expiryString}&sn=${encodedSignature}`
The above snippet returns me something like this:
SharedAccessSignature uid=integration&ex=2021-04-21T10:48:04.402Z&sn=**O8KZAh9zVHw6Dmb03t1xlhTnrmP1B6i+5lbhQWe**= (Some characters hidden for security, but number of characters is real)
Note that there is only one trailing dash = in the above mentioned SAS token, whereas SAS Tokens in all examples and manually created SAS Token from API Management Portal have 2 dashes ==
Is there anything I am doing wrong?
Thanks in advance.
According to the document of SAS token for Azure APIM, we can see the sample is c# code:
The difference between the sample and your code is the c# sample uses HMACSHA512 but your code use HMAS256. So I think you also need to use HMACSHA512 in your nodejs. You can do it like:
var hash = crypto.createHmac('sha512', key);
You may also need to do hash.update(text); and hash.digest(), please refer to this document about it.
Thank you Hury Shen! I also figured out that we don't need crypto-js for (as we have to import an external library for that). Node has crypto as its native module and we can use that. The following JavaScript snippet works fine.
import crypto from "crypto"
const identifier = <YOUR_AZURE_APIM_IDENTIFIER>
const secretKey = <YOUR_AZURE_APIM_SECRET_KEY>
// setting token expiry time
const expiryDate = new Date(Date.now() + 1000 * 60 * 60 * 24 * 29)
const expiryString = expiryDate.toISOString().slice(0, -1) + "0000Z"
const dataToSign = `${identifier}\n${expiryString}`
// create signature
const signedData = crypto
.createHmac("sha512", secretKey)
.update(dataToSign)
.digest("base64")
// SAS Token
const accessToken = `SharedAccessSignature uid=${identifier}&ex=${expiryString}&sn=${signedData}`
I have a basic script that I found works pretty good for my needs. The file I want to copy gets updated once a day. The person that updates it just overwrites the file every day so I'm trying to store a historical copy of the file, into a folder on my drive. The script below does copy the file and creates the file for me. However I am trying to polish up a few things.
function DailyPerformanceCopy() {
ScriptApp.newTrigger('DailyPerformanceTrigger')
.forSpreadsheet('ENTERSPREADSHEETIDHERE')
.onEdit()
.create();
var date = new Date();
Logger.log(Utilities.formatDate(date,'America/Chicago', 'MMMM dd, yyyy'));
var ss = SpreadsheetApp.openById("ENTERSPREADSHEETIDHERE");
//Make a copy of the template file
var documentId = DriveApp.getFileById('ENTERSPREADSHEETIDHERE').makeCopy().getId();
//Rename the copied file
DriveApp.getFileById(documentId).setName('Performance ' + date);
}
I would like the copied file to only be saved as Performance + The current Month, Date, Year. (Performance March 16 2020. Tomorrows copy to be saved as Performance March 17 2020, etc)
Its currently being saved as: Performance Mon Mar 16 2020 14:45:09 GMT-0400 (Eastern Daylight Time)
Its currently being saved to the root of my drive. I'd like it to be saved to the folder I created called "Performance"
Im not sure if it will execute tomorrow after the file gets updated. Im assuming so?
function DailyPerformanceCopy() {
const ss=SpreadsheetApp.openById("**************ENTERSPREADSHEETIDHERE******************");
const dfldr=DriveApp.getFolderById('************Enter Folder Id***************')
if(notTrigger('DailyPerformanceTrigger')) {ScriptApp.newTrigger('DailyPerformanceTrigger').forSpreadsheet(ss.getId()).onEdit().create();}
const ts=Utilities.formatDate(new Date(),'America/Chicago', 'MMMM dd yyyy');
const file=DriveApp.getFileById(ss.getId());
const name=Utilities.formatString('Performance %s', ts);
file.makeCopy(name, dfldr);
}
function notTrigger(funcname) {
const tA=ScriptApp.getProjectTriggers();
for(var i=0;i<tA.length;i++) {if(tA[i].getHandlerFunction()=='funcname') {return true;}}
return false;
}
I've been trying to get Authorization for Amazon's s3 rest api going. It's pretty damn complicated.
Because I'm trying to make a simple GET request from an admin page on my website, I'm just trying to do this through Javascript. Here are the instructions for constructing the Signature for the Authorization header:
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ))
To keep us sane, they give us a few examples, with the following givens:
var AWSSecretAccessKey = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY,
StringToSign = 'GET\n\n\nTue, 27 Mar 2007 19:36:42 +0000\n/johnsmith/photos/puppy.jpg;'
The output for this in their docs is bWq2s1WEIj+Ydj0vQ697zp+IXMU=. Based on the following I am getting ZGVjNzNmNTE0MGU4OWQxYTg3NTg0M2MxZDM5NjIyZDI0MGQxZGY0ZQ==:
function encode_utf8(s) {
return unescape(encodeURIComponent(s));
}
I used code.google.com's CryptoJS.HmacSHA1 function for the SHA1 hashing. My final Signature function looks like this:
var signature = btoa( CryptoJS.HmacSHA1( aws_secret, encode_utf8( StringToSign) ) );
What is going wrong here???
I actually found the answer from an SO question with reference to google's older (2.0) CrytpoJs library. You need:
2.0.0-crypto-sha1.js
2.0.0-hmac-min.js
Then you create your signature as so:
Signature = btoa( Crypto.HMAC(Crypto.SHA1, encode_utf8(StringToSign), aws_secret, { asString: true }) )
I couldn't find a way to to get Strings instead of Bits in the new version.
When I use toLocaleDateString in browser it returns
n = new Date()
n.toLocaleDateString()
"2/10/2013"
but in node.js the format is completely different
n = new Date()
> n.toLocaleDateString()
'Sunday, February 10, 2013'
How to get the browser's format (mm/dd/yy) in node.js?
For me, the solution was to install an additional module full-icu for node js
full-icu-npm
And after in package.json insert:
{"scripts":{"start":"node --icu-data-dir=node_modules/full-icu YOURAPP.js"}}
or
In the current version of Node.js v10.9.0 is Internationalization Support.
To control how ICU is used in Node.js, you can configure options are available during compilation.
If the small-icu option is used you need to provide ICU data at runtime:
the NODE_ICU_DATA environment variable:
env NODE_ICU_DATA=/some/directory node
the --icu-data-dir CLI parameter:
node --icu-data-dir=/some/directory
I also found this broken in node.JS. For example, in node console, type
new Date().toLocaleDateString('en-GB')
it still displays US format. Using Werner's method above, you can override default Date.toLocaleDateString() behavior for your locale:
Date.prototype.toLocaleDateString = function () {
return `${this.getDate()}/${this.getMonth() + 1}/${this.getFullYear()}`;
};
UPDATE: 2021-12-01
Starting with Node.js v13.0.0, releases are now built with default full-icu support. So this shouldn't be a problem with later releases.
in node.JS you can not get browser's format.
Node.JS runs on Server-side.
You have to do date formatting before displaying it in browser at your client side JS framework.
Date.prototype.toLocaleDateString = function () {
var d = new Date();
return (d.getMonth() + 1) + "/" + d.getDate() + "/" + d.getFullYear();
};
I need to obtain current time (from a credible source) using JSON. Precise time is mission-critical in my application so I cannot rely on the time of the device, even if it is only a second or two off.
EDIT: I am not as worried about 'precision' rather just so that several devices running the app have the same time.
As of Jan. 07th 2020 http://worldtimeapi.org/ is working fine. we can get current date and time details for specfic time-zone or ip address easily in either json format or plain text format.
http://worldtimeapi.org/api/timezone/America/Santiago
the above url will give you the current date and time details in json for "America/Santiago".
http://worldtimeapi.org/api/timezone/Asia/Kolkata
the above url will give you the current date and time details in json for "Asia/Kolkata".
Request the current time based on your public IP (as JSON):
$ curl "http://worldtimeapi.org/api/ip"
Note: by default, the API returns JSON. Adding a suffix of .txt to any API URL will return a plain-text response, which may be easier to parse on some systems.
function getTime(zone, success) {
var url = 'http://json-time.appspot.com/time.json?tz=' + zone,
ud = 'json' + (+new Date());
window[ud]= function(o){
success && success(new Date(o.datetime));
};
document.getElementsByTagName('head')[0].appendChild((function(){
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = url + '&callback=' + ud;
return s;
})());
}
getTime('GMT', function(time){
// This is where you do whatever you want with the time:
alert(time);
});
from here
As of Sept. 12th 2015 http://www.timeapi.org/utc/now.json seems to be working.
{"dateString":"2015-09-12T23:15:56+01:00"}
More information here http://www.timeapi.org. It's hosted on Heroku and the source is on Github.
The worldtimeapi.org is working just fine. If you will be using Javascript:
const zone = 'Europe/Lisbon'
fetch('https://worldtimeapi.org/api/timezone/' + zone)
.then(r => r.json())
.then(r => {
// strip out timezone offset from datetime ISO string
const d = new Date(r.datetime.replace(/[+-]\d\d:\d\d$/, ''))
console.log(`Time now in ${zone}: ${d.getHours()}:${d.getMinutes()}`)
})