InfoSpace - Signing Code Samples
SDK Home | Client Side Results SDK - Home
Page last modified: 16 Oct 2017

Sample Code for Signing Requests

Restrictions

DO NOT USE JAVASCRIPT TO GENERATE THE SIGNATURE!

  • Using JavaScript will result in invalid signatures

Code Samples

C#

using System;
using System.Security.Cryptography;
using System.Text;
using System.Web;

class InfoSpaceRequestSigner
{
    private readonly string token;

    public InfoSpaceRequestSigner(string token)
    {
        this.token = token;
    }

    public string GetSignature(string queryTerm)
    {
        string value =
            GetTimeToNearestMinute() +
            this.token +
            queryTerm;

        return HashValue(value);
    }

    private string GetTimeToNearestMinute()
    {
        return DateTime
            .UtcNow
            .AddSeconds(30)
            .ToString("yyyyMMddHHmm");
    }

    private string HashValue(string value)
    {
        byte[] bytes = Encoding.UTF8.GetBytes(value);

        using (SHA1 hash = SHA1.Create())
        {
            bytes = hash.ComputeHash(bytes);
        }

        return EncodeUrlSafeBase64(bytes);
    }

    private string EncodeUrlSafeBase64(byte[] bytes)
    {
        string signature = HttpServerUtility.UrlTokenEncode(bytes);

        // Remove padding integer (C# specific):
        return signature.Remove(signature.Length - 1);
    }
}

Java

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import org.apache.commons.codec.binary.Base64;

public class InfoSpaceRequestSigner
{
    private final String token;
    
    public InfoSpaceRequestSigner(String token)
    {
        this.token = token;
    }

    public String getSignature(String queryTerm)
    {
        String value =
            getFormattedDateString() +
            this.token +
            queryTerm;
        
        return hashValue(value);
    }
    
    private String getFormattedDateString()
    {
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmm");
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        
        return format.format(getTimeToNearestMinute());
    }
    
    private Date getTimeToNearestMinute()
    {
        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        
        cal.add(Calendar.SECOND, 30);
        
        return cal.getTime();
    }
    
    private String hashValue(String input)
    {
        byte[] bytes = decodeUTF8(input);
        
        bytes = hashSHA1(bytes);
        
        return encodeUrlSafeBase64(bytes);
    }
    
    private byte[] decodeUTF8(String input)
    {
        try
        {
            return input.getBytes("UTF-8");
        }
        catch (UnsupportedEncodingException e)
        {
            return input.getBytes();
        }
    }
    
    private byte[] hashSHA1(byte[] input)
    {
        try
        {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            
            return digest.digest(input);
        }
        catch (NoSuchAlgorithmException e)
        {
            return null;
        }
    }
    
    private String encodeUrlSafeBase64(byte[] input)
    {
        return Base64.encodeBase64URLSafeString(input);
    }
}

PHP

<?php
 
/**
 * PHP implementation of InfoSpaceRequestSigner
 *
 * @see http://sdk.infospace.com/access-keys/#ExampleCode
 *
 */
class InfoSpaceRequestSigner {
    private $token;
 
    /**
     * Constructs the object and saves the token
     *
     * @param string $token
     */
    public function __construct($token) {
        $this->token = $token;
    }

    public function getSignature($queryTerm) {
        $value = $this->getFormattedDateString() . $this->token . $queryTerm;
        return $this->hashValue($value);
    }
 
    /**
     * Gets the date/time to the nearest minute as YYYYMMDDHHMM
     *
     * @return string
     */
    private function getFormattedDateString() {
        // Save the current timezone, get a date as GMT and reset timezone
        $timezone = date_default_timezone_get();
        date_default_timezone_set('GMT');
        $datetime = date('YmdHi', $this->getTimeToNearestMinute());
        date_default_timezone_set($timezone);
 
        return $datetime;
    }
 
    /**
     * Gets the date/time +30 seconds as a unix timestamp
     *
     * @return int
     */
    private function getTimeToNearestMinute() {
        return time() + 30;
    }
 
    /**
     * Gets a base64 encoded SHA1 hash
     *
     * @param string $input
     *
     * @return string
     */
    private function hashValue($input) {
        $bytes = $this->hashSHA1($input);
 
        return $this->encodeUrlSafeBase64($bytes);
    }
 
    /**
     * Gets a SHA1 hash
     *
     * @param string $input
     *
     * @return string
     */
    private function hashSHA1($input) {
        // Trial and error shows this must be binary result
        return sha1($input, true);
    }
 
    /**
     * Creates a URL safe base64 encoded string
     *
     * @param string $input
     *
     * @return string
     */
    private function encodeUrlSafeBase64($input) {
        // Apache code replaces + with -, / with _ and trims padding (=)
        return str_replace(array('+', '/'), array('-', '_'), trim(base64_encode($input), '=='));
    }
}

RUBY

#!/usr/bin/ruby
# INSP.rb
require 'base64'
require 'digest/sha1'
require 'open-uri'

class InfoSpaceRequestSigner
    @token = ''

    # Constructs the object and saves the token   
    # param string token
    def initialize(token)
        @token = token
    end

    def get_signature(queryTerm)
        signature = get_time_to_nearest_minute() + @token + queryTerm       
        return hash_value(signature.to_s)
    end

    # Gets the date/time to the nearest minute as YYYYMMDDHHMM
    # return string     
    def get_time_to_nearest_minute
        (Time.now.utc + 30).strftime("%Y%m%d%H%M")
    end

    # Gets a base64 encoded SHA1 hash
    # param string value
    # return string
    def hash_value(value)
        binary_sig = value.encode('utf-8')  # Ruby1.9+

        hashed_sig = Digest::SHA1.digest(binary_sig)
        
        self.encode_url_safe_base64(hashed_sig)
    end

    # Creates a URL safe base64 encoded string
    # param string bytes
    # return string
    def encode_url_safe_base64(bytes)
        base64encoded = Base64.encode64(bytes).chop
        uri_enc = URI.encode(base64encoded)

        signature = uri_enc.gsub '+', '-' # replace plus with dash      
        signature = signature.gsub '/', '_' # replace slash with an underscore

        signature.chop
    end

    def display_token
        print "Token: #{@token}"
    end

end

PYTHON

#!/usr/bin/python

import datetime
import hashlib
import base64
#import urllib # only needed for python 2.x
import urllib.parse # only needed for python 3.x

class InfoSpaceRequestSigner:
    _token = ''

    # Constructs the object and saves the token   
    # param string token
    def __init__(self, token):
        self._token = token

    def get_signature(self, queryTerm):
        signature = "%s%s%s" % (self.get_time_to_nearest_minute(), self._token, queryTerm);     
        return self.hash_value(signature)

    # Gets the date/time to the nearest minute as YYYYMMDDHHMM
    # return string     
    def get_time_to_nearest_minute(self):
        dt = self.add_seconds(datetime.datetime.utcnow(), 30)
        return dt.strftime("%Y%m%d%H%M")

    # Add seconds to datetime object.
    def add_seconds(self, tm, secs):
        fulldate = datetime.datetime(tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second)
        fulldate = fulldate + datetime.timedelta(seconds=secs)
        return fulldate

    # Gets a base64 encoded SHA1 hash
    # param string value
    # return string
    def hash_value(self, value):
        binary_sig = value.encode('utf-8')
        hashed_signature = hashlib.sha1(binary_sig).digest()
        return self.encode_url_safe_base64(hashed_signature);

    # Creates a URL safe base64 encoded string
    # param string bytes
    # return string
    def encode_url_safe_base64(self, bytes):
        base64encoded = base64.b64encode(bytes)
        #enc_signature = urllib.quote(base64encoded) # use with python 2.x
        enc_signature = urllib.parse.quote(base64encoded) # use with python 3.x
        signature = enc_signature.rstrip(enc_signature[-3:]) # remove padding
        signature = signature.replace("%2B", '-') # replace slash with underscore
        signature = signature.replace('/', '_') # replace slash with underscore
        return signature

    def display_token(self):
        print ("Token: ", self._token)        
        

Alternative base64url encoders

While the preceding code sample demonstrates an implementation of the Apache Commons base64url encoder, there are several other alternative implementations that may be more appropriate. Also, if your commons-codec jar is from a Google jar (e.g. Adwords API) ensure you have the updated version. Previous versions of the commons-codec will cause a java.lang.NoSuchMethodError on runtime (compilation is just fine).

Apache Commons
http://commons.apache.org/proper/commons-codec//apidocs/org/apache/commons/codec/binary/Base64.html#encodeBase64URLSafeString(byte\[\])


import org.apache.commons.codec.binary.Base64;
Base64.encodeBase64URLSafeString(byte[]);

Android
http://developer.android.com/reference/android/util/Base64.html


import android.util.Base64;
Base64.encode(byte[], NO_PADDING | URL_SAFE);

Guava
http://docs.guava-libraries.googlecode.com/git-history/v14.0/javadoc/index.html?com/google/common/io/BaseEncoding.html


import com.google.common.io.BaseEncoding;
BaseEncoding.base64url().omitPadding().encode(bytes[]);