A cryptographic hash is an algorithm that takes required data to be encrypted and produces enciphered text called "hash". There are different algorithms available in which SHA-256 is way better because it produces a different hash every time even if the input is the same. SHA-256 is more secure and fast enough to be used to secure HTTP calls. So as an API owners, we always want to secure our API endpoints from being attacked. In this blog we will see how can a user securely call an API endpoint via adding a hash to the request header and how can an API owner verify that hash at his end. So let's start.
# Steps to secure an API request:
- Generate your public and private key pair: Obtain your public/private key pair as an API user. Here we will use a private key to generate the hash value and sign the API request. A private key is shared only with the key creator and never be shared with anyone whereas the public key is sharable. Share this public key with the API gateway so that later on the gateway will use the User's public key to validate the hash.
- Prepare the content to be hashed: Now we will collect a few properties of the API like HTTP method (GET/POST), HTTP URL, request time, and client id or API key of API gateway and then prepare the content to be hashed according to the syntax given below:
{HTTP METHOD}+{HTTP URL}+{API REQUEST TIME}+{CLIENT ID}
- Generate the hash using SHA256withRSA: After preparing the content that is to be hashed we will produce the actual hash using SHA256withRSA. Here I wrote a go function that is returning the hash value.
func CreateSignature(httpUrl string, httpMethod string, apiRequestTime string) string {
//prepare rsa key
PEMBlock, _ := pem.Decode([]byte(constants.PrivateKey))
keyBytes, _ := x509.ParsePKCS8PrivateKey(PEMBlock.Bytes)
privateKey, _ := keyBytes.(*rsa.PrivateKey)
//prepare content to be hashed
digestStr := httpMethod + ` ` + httpUrl + ` ` + apiRequestTime + ` ` + constants.ClientId
contentToBeSigned := []byte(digestStr)
//calculate the SHA256 checksum of the data
digest := sha256.Sum256(contentToBeSigned)
signature, _ := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, digest[:])
encodedSignature := base64.StdEncoding.EncodeToString(signature)
hash := url.QueryEscape(encodedSignature)
//return final hash
return hash
}- Add the hash to the request header: Now get the hash value returned from the above function and add it to the header of your request.
curl --location --request POST 'http://{api_url}' \
--header 'Signature: kMYkxXzFEX2zku7mkN8lSCR6yGuT1urXKGUU9NyxieSznkSsChAGo0n%2BqUQzYEcp9rnvEuz
0Y%2BHAl5X%2FSdhRa2mC%2FM%2BtrgmqJ8MEJCQPOXdag1xlV14kmhSbqfQOQx1evezzucbTLXI9BeIGWqPfw7o88xjbC
SKrhNEuDvtxvv0DXrRW%2B11My5%2BhSVm2P%2BqRIi9mF9WDhhtDnucY03cj%2BK4sUfeegh5z6x4ZTFw%2FqGJc4bfHO
%2F3iXN%2BUmb22bCIupDhejosrsi0%2BrrjKe9IR0eQ1D3VCoJNpJIwqdCyDHxNfShXyt5kEv8d7Bs4o0FDqlgJKmCyZs
xPJgzCAj1%2FgOg%3D%3D' \
--header 'Content-Type: application/json' \
--header 'Request-Time: 2022-03-16T07:39:55Z' \
--data-raw '{
"user_name": "test",
}'# Steps to validate a Hash:
- Obtain the public key of the user: From the user who is using your API will provide his platform's public key which we will use further to. verify the hash present in the request header.
- Prepare the content to be hashed: Now prepare the content that is being verified according to the syntax given below:
{HTTP METHOD}+{HTTP URL}+{API REQUEST TIME}+{CLIENT ID}
- Get the hash from the request header: Take out the hash string from the request header that is to be validated.
Signature: kMYkxXzFEX2zku7mkN8lSCR6yGuT1urXKGUU9NyxieSznkSsChAGo0n%2BqUQzYEcp9rnvEuz
0Y%2BHAl5X%2FSdhRa2mC%2FM%2BtrgmqJ8MEJCQPOXdag1xlV14kmhSbqfQOQx1evezzucbTLXI9BeIGWqPfw7o88xjbC
SKrhNEuDvtxvv0DXrRW%2B11My5%2BhSVm2P%2BqRIi9mF9WDhhtDnucY03cj%2BK4sUfeegh5z6x4ZTFw%2FqGJc4bfHO
%2F3iXN%2BUmb22bCIupDhejosrsi0%2BrrjKe9IR0eQ1D3VCoJNpJIwqdCyDHxNfShXyt5kEv8d7Bs4o0FDqlgJKmCyZs
xPJgzCAj1%2FgOg%3D%3D
- Verify the hash:
func (validateSignature ValidateSignature) ValidateRequestSignature(headers map[string]string,
httpUrl string, httpMethod string) (bool, error) {
//prepare rsa key
antVerifyKey := os.Getenv("PlatformPublicKey")
antVerifyKey = strings.Replace(antVerifyKey, `\n`, "\n", 5)
PEMBlock, _ := pem.Decode([]byte(antVerifyKey))
keyBytes, _ := x509.ParsePKIXPublicKey(PEMBlock.Bytes)
publicKey, _ := keyBytes.(*rsa.PublicKey)
//decode hash
urlDecodedHash, _ := url.QueryUnescape(headers["signature"])
decodedHash, _ := base64.StdEncoding.DecodeString(urlDecodedHash)
//create comparing string
digestStr := httpMethod + ` ` + httpUrl + ` ` + headers["Request-Time"] + ` ` + constants.ClientId
//calculate the SHA256 checksum of the data
digest := sha256.Sum256([]byte(digestStr))
//verify the hashed content
verify := rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, digest[:], decodedHash)
if verify != nil {
return false, errors.InvalidSignature
}
return true, nil
}
Signature: kMYkxXzFEX2zku7mkN8lSCR6yGuT1urXKGUU9NyxieSznkSsChAGo0n%2BqUQzYEcp9rnvEuz 0Y%2BHAl5X%2FSdhRa2mC%2FM%2BtrgmqJ8MEJCQPOXdag1xlV14kmhSbqfQOQx1evezzucbTLXI9BeIGWqPfw7o88xjbC SKrhNEuDvtxvv0DXrRW%2B11My5%2BhSVm2P%2BqRIi9mF9WDhhtDnucY03cj%2BK4sUfeegh5z6x4ZTFw%2FqGJc4bfHO %2F3iXN%2BUmb22bCIupDhejosrsi0%2BrrjKe9IR0eQ1D3VCoJNpJIwqdCyDHxNfShXyt5kEv8d7Bs4o0FDqlgJKmCyZs xPJgzCAj1%2FgOg%3D%3D
func (validateSignature ValidateSignature) ValidateRequestSignature(headers map[string]string,
httpUrl string, httpMethod string) (bool, error) {
//prepare rsa key
antVerifyKey := os.Getenv("PlatformPublicKey")
antVerifyKey = strings.Replace(antVerifyKey, `\n`, "\n", 5)
PEMBlock, _ := pem.Decode([]byte(antVerifyKey))
keyBytes, _ := x509.ParsePKIXPublicKey(PEMBlock.Bytes)
publicKey, _ := keyBytes.(*rsa.PublicKey)
//decode hash
urlDecodedHash, _ := url.QueryUnescape(headers["signature"])
decodedHash, _ := base64.StdEncoding.DecodeString(urlDecodedHash)
//create comparing string
digestStr := httpMethod + ` ` + httpUrl + ` ` + headers["Request-Time"] + ` ` + constants.ClientId
//calculate the SHA256 checksum of the data
digest := sha256.Sum256([]byte(digestStr))
//verify the hashed content
verify := rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, digest[:], decodedHash)
if verify != nil {
return false, errors.InvalidSignature
}
return true, nil
}That's it............. :)

Comments
Post a Comment