• ViVOpay 4880 is the latest addition to our contactless family with MagStripe, EMV Contact and Contactless.
  • LEARN MORE

Welcome to ID Tech Products Blog!

  • Home
    Home This is where you can find all the blog posts throughout the site.
  • Categories
    Categories Displays a list of categories from this blog.
  • Tags
    Tags Displays a list of tags that have been used in the blog.
  • Bloggers
    Bloggers Search for your favorite blogger from this site.
  • Team Blogs
    Team Blogs Find your favorite team blogs here.
  • Login
    Login Login form
Recent blog posts

You can connect to any USB card reader from any web browser using Websockets and NodeJS. Which means you can do EMV transactions from a Virtual Terminal. Find out how, below!

 
Last time, I showed how to set up USB connectivity via JavaScript using NodeJS and a module called node-hid. The result was that we were able to get programmatic access to USB devices, from JavaScript, in only about 75 lines of code. Our script implemented automatic device detection and connection, and got us a device handle with which to do reading and writing of USB data. 

All of which is fine, if the only thing you need is programmatic access to USB data from within a Node script.

But what if you want to communicate that data to another process? What if (for example) you want to send USB data to a web browser? Or push it out to a server?

No problem. You can do it. It's easy! b2ap3_thumbnail_virtual-terminal.jpg

Websockets to the Rescue

The Websocket Protocol (IETF spec here) is one of the most amazingly useful Web standards to come down the pike in a long, long time. And happily, it's implemented by all modern browsers. Plus, it's available to Node scripts. Which means your Node code can talk to your browser scripts quite easily, if you just create a socket connection that bridges the two worlds.

On the Node side, you're going to need to open an OS console and run npm install socket.io -g (one time only) to install the very widely used and justifiably popular socket.io module. After you do this, your scripts can call require("socket.io") to take advantage of that module's incredible power. (More of which, in just a second.)

On the browser side, you need to point to the socket.io.slim.js script, so that your web page can slurp Websocket data. (Don't try to use the standard browser Websocket API for this, because the aforementioned socket.io module uses its own keep-alive scheme that isn't known to your browser. To avoid conflicts, use the socket.io.slim.js client-side script. As a side-benefit, the script comes with polyfills that give older browsers Websocket compatibility.) The easiest way to get the client-side script is to drag it down from an edge server (CDN) by putting the following snippet in your web page:

    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.3/socket.io.slim.js"></script>

With that script in place, your web page is ready to connect to sockets served by your very own socket server.

Serving Websockets

Let's talk about how to set up a socket server in loopback mode (which is to say, on ws://localhost). It's unbelievably simple, and incredibly handy for inter-process communication.

You need Node, of course, and you need the socket.io module (as described above). Then you need to launch Node and have it run a script like the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//  ===========  socket-provider  ============
// Dependency: This module uses the socket.io module.
// There is a call to require("socket.io") further below.
  
/* Usage:
  
   SocketProvider = require('socket-provider');
   var sp = new SocketProvider();
   sp.setPort( 9001 );
   var server = sp.createServer();
  
   (clients can now connect on port 9001)
  
   In WEB PAGE (with socket.io.slim.js loaded), do:
  
   var port = 9001;
   var socket = io.connect('http://localhost:'+port);
   if ( socket )
       socket.on('message',
          function(msg) { ...do something with msg... });
   // Or send messages:
   socket.emit( 'echo', something );
  
   TO CREATE A NODE CLIENT (running headless in Node!):
   socket = require( 'socket.io-client' );
   client = socket( "ws://localhost:9001" );
   client.on('message', handlerFunction ); // handle events
   client.emit( 'message', someObject ); // send messages
  
*/
  
class SocketProvider {
   
        constructor() {
                 
                var PORT = null;     
                this.setPort = function( p ) { PORT = p; };
                this.createServer = function( port ) {
   
                        // Create do-nothing http server
                        var server = require('http').createServer();
   
                        // require the socket.io module and attach to server:
                        var io = require('socket.io').listen(server);
                        
                        // on client connection we don't do much
                        io.sockets.on('connection', function (connection) {
                          
                                console.log('A client connected...');
                                connection.emit('message', 'Connected!');                             
                                
                                // Set up disconnect handler
                                connection.on('disconnect',  ()=> { 
					   console.log('Client disconnected'); 
					} );
 
                                // broadcast 'echo' messages
                                connection.on('echo', (m)=>{
                                     io.sockets.emit('message',  m);                              
                                });
                        }); // on connection
                   
                        //      OK, now let's listen for socket requests
                        server.listen( port || PORT );
                        return io.sockets; // return instance of socket server
   
                };  // createServer()
        } // constructor
}; // class

Disregarding comments, we're only looking at about 30 lines of code here. Conceptually, what's going on is that we require('http') so that we can set up a dummy HTTP server instance. Then we require('socket.io').listen(server) so that the socket.io module can upgrade incoming HTTP requests to Websocket connections. 

By default, Websocket connections enable full-duplex, message-based, point-to-point communication between connected parties. But point-to-point doesn't mean messages automatically get broadcast to other connections. To enable broadcasting, we register a custom event listener, the on.('echo') listener at line 58, which re-emits incoming "echo" messages onto all socket connections, as a new message of type 'message'. So the rule here is: To do point-to-point messaging, use the 'message' event type, but to make sure the socket server passes your message on to all listeners, use the 'echo' message type. (This is our own convention, btw, not something imposed by socket.io.)

The final thing to notice is that the above code, by itself, doesn't do anything, because it's a class definition. You have to instantiate the class at runtime in order to use it. But that's super-easy. The comments at the top of the code explain how to use the class. Plus, we'll be seeing how to use it in upcoming blog posts.

Don't miss the next few posts in this series, because we're going to be doing contactless transactions against a live gateway in no time -- all from the browser, using JavaScript! Check back soon!

 

Hits: 638
0

Posted by on in Blog posts

A really handy thing to be able to do is to control USB devices using JavaScript. This is a powerful capability, made easily possible by NodeJS (better known, simply, as Node). To get Node, go here.   b2ap3_thumbnail_nodejs-logo.png

Node is an increasingly vital piece of infrastructure for companies that do business via the Web. You've heard of many of the companies I'm talking about. (Check out their logos below.)

Most companies that use Node run it on the server side. But it's easily possible (and definitely worthwhile) to run it on the client side, too. 

Unlike scripts written for web pages, scripts that run on Node have no security restrictions that would keep them from performing file I/O, opening network connections across domains, shelling out to the OS, or calling native code modules, among other things. Since they're unhampered by "sandbox security" restrictions, scripts that run on Node can do amazing, powerful things that browser scripts could never do.

b2ap3_thumbnail_WhoUsesNode.png
Companies that use Node for critical infrastructure.

When I wanted to connect to ID TECH's USB-based credit card readers using JavaScript, I looked to see what kind of USB connectivity modules might already exist for Node. Turns out there are several. I chose one called node-hid, which is based on the cross-platform HIDAPI library.

I chose node-hid mostly because of its brain-dead-simple API, but also based on the fact that it installs super-easily. Just fire up a command line console and run npm install node-hid. Node's npm package manager will go to work downloading all the bits you need from the Web, and it will resolve dependencies and build binaries automatically. (Allow a minute or two for this.) When you're done, you'll have the ability to put require 'node-hid' in your Node scripts and connect to USB devices!

Below is an example of a script you can run with Node (and node-hid) that will let you connect to USB devices having the ID TECH vendor ID (hex 0xACD):

// ==========  usb-provider ===========
'use strict';

var HID = require("node-hid");
const EventEmitter = require('events');

class USBProvider extends EventEmitter {

    constructor() {
        super();
        var self = this;
        this.onerror = function(e) {
            console.log('error: ' + e);
        };
        this.getDeviceHandle = function() {
            return deviceHandle;
        }

        var SCAN_INTERVAL = 2000; // scan every 2 secs
        var VENDOR_ID = 0xACD; // default ID TECH vid
        var deviceHandle = null; // stores our handle
        var deviceRecord = null; // stores device record
        var stopKey = null; // to stop polling (if needed)

        // This will be called repeatedly by poll(), below
        function cycle() {

            var deviceFound = false;
            HID.devices().forEach(function(device, index, records) {

                deviceFound = (device.vendorId == VENDOR_ID);

                if (device.vendorId == VENDOR_ID && deviceRecord == null) {
                    deviceRecord = device;
                    try {
                        // Try to connect.
                        deviceHandle =
                            new HID.HID(device.vendorId, device.productId);

                        deviceHandle.on('error', self.onerror);

                        self.emit('usbconnect', deviceHandle);

                        console.log("usbprovider: connect");
                    } catch (e) {
                        self.onerror("Exception caught:\n" + e);
                        self.emit('usbexception', device);
                    }
                } // if

                if (index == records.length - 1 && !deviceFound) {

                    // HANDLE DISCONNECT EVENT
                    if (deviceRecord != null) {
                        deviceRecord = deviceHandle = null; // nullify record	
                        // self.ondisconnect();
                        self.emit('usbdisconnect');

                        console.log("usbprovider: disconnect");
                    } // if 

                } // if 
}); // forEach } // cycle this.poll = function() { this.stopKey = setInterval(cycle, SCAN_INTERVAL); } } }
// Allow other modules to use this one: module.exports = USBProvider;

We want to inherit the methods of Node's EventEmitter class (so that we can emit custom events). Therefore we start right out by declaring a class that extends EventEmitter. (Note that Node allows the use of EMCA 6 class-declaration syntax.)

We have a poll() method (near the bottom) that uses setInterval() to start an endless polling cycle that has us checking the device list once every two seconds. The cycle() method carries out our poll. It checks every connected device using two 'if' statements. The first 'if' says: If any device with the ID TECH VendorID (0x0ACD) is found, and we're not already connected to a device of interest, we go ahead and try connecting to the device. The second 'if' says: If the poll finds no ID TECH device and yet we already have a non-null deviceRecord, it means the record is stale because the device is unplugged. (Hence we pop a 'usbdisconnect' event.) 

The USBProvider emits 'usbconnect' and 'usbdisconnect' events, plus 'data' events. When you get a 'usbconnect' event, it's important to realize you're being passed the deviceHandle for the connected device. You need to store a reference to that handle right away, so you can later use it to write to the device. But there's also another thing you need to do right away, which is register your data handler. This lets you read from the device. (Reading happens via 'data' events.) Register your 'data' handler at connect time.

Code that uses the USBProvider class might look similar to this:

// First, instantiate the provider
var usb = new USBProvider();

var deviceHandle = null; // We will store the device handle here

// Set up a connection handler. Inside it, set the data handler.
usb.on('usbconnect', function(h) {

    deviceHandle = h; // cache the handle

    // set up a data handler (for reading data)
    deviceHandle.on('data', (data) => {

        var hex = data.toString('hex');
        // do something with data...
    });

});

Notice that we can easily get a hex string version of our data using toString('hex'). That's because the data is in a Buffer object. The Buffer object is not a JavaScript array. 

Elsewhere in your code, you can write to the device very easily, as follows:

    deviceHandle.write( cmdArray );

The cmdArray isn't a Buffer object. It is simply a JavaScript array containing integer values.

But don't forget, you can't connect to anything at all unless you start up the poll() method! So after setting up your event handlers and initializing your code, remember to begin polling:

    usb.poll();  // Be sure to do this! This starts auto-detect & connect

With the various bits of code shown above, you'll be able to read and write USB-based ID TECH devices in no time. And if you want, you can use Node's wonderful Websockets capability to send USB data straight to the browser, so that your HTML page can control your payment device. I'll have a lot more to say on that very subject in future posts. So check back here soon!

Hits: 2623
0

Posted by on in Blog posts

One of the nice things about chip cards (ICCs) is that the data that comes out of them is virtually always supplied in a standard format, called BER-TLV. In plain English: Basic Encoding Rules, Tag-Length-Value (a quaint but informative article about it can be found here).

The BER-TLV format is one of the ASN.1 (Abstract Syntax Notation) encodings defined by ITU X.690, which is a very old set of standards dating to the primordial predawn of the Internet.

Chip cards use the TLV scheme to encode card data. At its simplest, the Tag-Length-Value scheme just means that if you have a tag called (say) "5A" and its value is 8 octets represented by (for example) successive hex values "41 11 12 34 56 78 9A BC," then the TLV encoding will look like 5A084111123456789ABC, where 5A is the tag, 08 is the length, and 4111123456789ABC is the value.

EMVCo (the card-issuer consortium behind the whole chip-card thing) defines a bunch of standard tags for chip-card transactions. For example 5A always encodes the PAN (primary account number, or card number), 9F02 encodes the Authorized Amount of a transaction, 5F2D encodes Language Preference, and so on. The complete list of EMVco-defined tags (and their meanings) can be found at https://www.eftlab.co.uk/index.php/site-map/knowledge-base/145-emv-nfc-tags.

Given that TLVs encode their own length, it should be a snap to parse TLV data, right?

Well, yes. Mostly. Kind of.

If every tag had a simple one-byte identifier (like 5A), it really would be super-duper-easy to parse a TLV stream. But the TLV scheme wouldn't be very useful if identifiers could only ever take on one of just 256 possible values.

To make tag identifiers extensible, Basic Encoding Rules allow for the possibility of multi-byte tags. The rules say that if the bottom 5 bits of the first tag byte are set, then more tag-identifier bytes follow. In subsequent bytes, the top bit is set if more bytes follow, whereas the top bit is zero in the final byte. So for example, 5F24 is a legal 2-byte tag identifier, DFEF01 is a legal 3-byte tag, and so on.

EMVCo (which incorporates BER-TLV by reference in Book 3, Annex B, of the EMV specifications) also allows for the concept of "wrapper" tags, to enable hierarchical parent-child relationships (or nesting) among TLVs. Under EMV rules, if the sixth bit of a tag's first byte is set, the tag is said to be "constructed" (I prefer the term compound). Thus, a 3-byte tag FFEE01 could be used to wrap (fictional) TLVs of 3F0188 and 3F025544 as follows: FFEE01073F01883F025544. The parent tag, FFEE01, has 7 bytes of data, consisting of a 3-byte TLV and a 4-byte TLV. Groups of tags can be nested to any desired depth using this scheme.

Note carefully, the Length byte of a TLV can also be multi-byte. Here, the extensibility rule (taken from EMV Book 3 Annex B2) is:

b2ap3_thumbnail_BER-TLV.png

A length byte with the top bit set will mean you have to treat the bottom 7 bits as the "length of the Length." In other words, a Length byte of 0x82 means that there are two bytes of Length info (in the two bytes that follow). In the (fictional) TLV represented by 5F0F8103AABBCC, the tag is 5F0F, the length of the Length is one byte, the actual Length is 3 bytes, and the Value is AABBCC.

Clear as mud, right?

So, knowing all this, we're able to create a fully general recursive-descent TLV parser in about 75 lines of JavaScript, as follows.

//  ===============  BER-TLV PARSER  ================

// All known tags (EMVCo and ID TECH):
_KnownTags = 
{"42":true,"50":true,"52":true,"56":true,"57":true,"61":true,"62":true,"70":true,"71":true,"72":true,"73":true,"77":true,"80":true,"81":true,"82":true,"83":true,"84":true,"86":true,"87":true,"88":true,"89":true,"90":true,"91":true,"92":true,"93":true,"94":true,"95":true,"97":true,"98":true,"99":true,"4F":true,"5A":true,"5D":true,"5F20":true,"5F24":true,"5F25":true,"5F28":true,"5F2A":true,"5F2D":true,"5F30":true,"5F34":true,"5F36":true,"5F3C":true,"5F3D":true,"5F50":true,"5F53":true,"5F54":true,"5F55":true,"5F56":true,"5F57":true,"6F":true,"8A":true,"8C":true,"8D":true,"8E":true,"8F":true,"9A":true,"9B":true,"9C":true,"9D":true,"9F01":true,"9F02":true,"9F03":true,"9F04":true,"9F05":true,"9F06":true,"9F07":true,"9F08":true,"9F09":true,"9F0B":true,"9F0D":true,"9F0E":true,"9F0F":true,"9F10":true,"9F11":true,"9F12":true,"9F13":true,"9F14":true,"9F15":true,"9F16":true,"9F17":true,"9F18":true,"9F19":true,"9F1A":true,"9F1B":true,"9F1C":true,"9F1D":true,"9F1E":true,"9F1F":true,"9F20":true,"9F21":true,"9F22":true,"9F23":true,"9F25":true,"9F26":true,"9F27":true,"9F28":true,"9F29":true,"9F2A":true,"9F2B":true,"9F2D":true,"9F2E":true,"9F2F":true,"9F32":true,"9F33":true,"9F34":true,"9F35":true,"9F36":true,"9F37":true,"9F38":true,"9F39":true,"9F3A":true,"9F3B":true,"9F3C":true,"9F3D":true,"9F40":true,"9F41":true,"9F42":true,"9F43":true,"9F44":true,"9F45":true,"9F46":true,"9F47":true,"9F48":true,"9F49":true,"9F4A":true,"9F4B":true,"9F4C":true,"9F4D":true,"9F4E":true,"9F4F":true,"9F50":true,"9F51":true,"9F52":true,"9F53":true,"9F54":true,"9F55":true,"9F56":true,"9F57":true,"9F58":true,"9F59":true,"9F5A":true,"9F5B":true,"9F5C":true,"9F5D":true,"9F5E":true,"9F5F":true,"9F60":true,"9F61":true,"9F62":true,"9F63":true,"9F64":true,"9F65":true,"9F66":true,"9F67":true,"9F68":true,"9F69":true,"9F6A":true,"9F6B":true,"9F6C":true,"9F6D":true,"9F6E":true,"9F6F":true,"9F70":true,"9F71":true,"9F72":true,"9F73":true,"9F74":true,"9F75":true,"9F76":true,"9F77":true,"9F78":true,"9F79":true,"9F7A":true,"9F7B":true,"9F7C":true,"9F7D":true,"9F7E":true,"9F7F":true,"A5":true,"BF0C":true,"BF50":true,"BF60":true,"C3":true,"C4":true,"C5":true,"C6":true,"C7":true,"C8":true,"C9":true,"CA":true,"CB":true,"CD":true,"CE":true,"CF":true,"D1":true,"D2":true,"D3":true,"D5":true,"D6":true,"D7":true,"D8":true,"D9":true,"DA":true,"DB":true,"DC":true,"DD":true,"DF01":true,"DF02":true,"DF03":true,"DF04":true,"DF05":true,"DF06":true,"DF07":true,"DF08":true,"DF09":true,"DF0B":true,"DF0C":true,"DF0D":true,"DF0E":true,"DF0F":true,"DF10":true,"DF11":true,"DF12":true,"DF13":true,"DF14":true,"DF15":true,"DF16":true,"DF17":true,"DF18":true,"DF19":true,"DF1F":true,"DF20":true,"DF21":true,"DF22":true,"DF23":true,"DF24":true,"DF25":true,"DF26":true,"DF27":true,"DF28":true,"DF29":true,"DF2A":true,"DF2B":true,"DF2C":true,"DF30":true,"DF31":true,"DF32":true,"DF33":true,"DF40":true,"DF41":true,"DF42":true,"DF43":true,"DF44":true,"DF45":true,"DF46":true,"DF47":true,"DF48":true,"DF49":true,"DF4A":true,"DF4B":true,"DF4C":true,"DF4D":true,"DF4E":true,"DF4F":true,"DF50":true,"DF51":true,"DF52":true,"DF53":true,"DF54":true,"DF55":true,"DF56":true,"DF57":true,"DF58":true,"DF5A":true,"DF5B":true,"DF5C":true,"DF5D":true,"DF5E":true,"DF5F":true,"DF60":true,"DF61":true,"DF62":true,"DF63":true,"DF64":true,"DF65":true,"DF66":true,"DF68":true,"DF69":true,"DF6A":true,"DF6B":true,"DF6C":true,"DF6D":true,"DF6E":true,"DF6F":true,"DF70":true,"DF71":true,"DF72":true,"DF73":true,"DF74":true,"DF75":true,"DF76":true,"DF77":true,"DF78":true,"DF79":true,"DF7A":true,"DF7B":true,"DF7C":true,"DF7D":true,"DF7F":true,"DF8101":true,"DF8102":true,"DF8104":true,"DF8105":true,"DF8106":true,"DF8107":true,"DF8108":true,"DF8109":true,"DF810A":true,"DF810B":true,"DF810C":true,"DF810D":true,"DF810E":true,"DF810F":true,"DF8110":true,"DF8111":true,"DF8112":true,"DF8113":true,"DF8114":true,"DF8115":true,"DF8116":true,"DF8117":true,"DF8118":true,"DF8119":true,"DF811A":true,"DF811B":true,"DF811C":true,"DF811D":true,"DF811E":true,"DF811F":true,"DF8120":true,"DF8121":true,"DF8122":true,"DF8123":true,"DF8124":true,"DF8125":true,"DF8126":true,"DF8127":true,"DF8128":true,"DF8129":true,"DF812A":true,"DF812B":true,"DF812C":true,"DF812D":true,"DF8130":true,"DF8131":true,"DFDE04":true,"DFEE12":true,"DFEE15":true,"DFEE16":true,"DFEE17":true,"DFEE18":true,"DFEE19":true,"DFEE1A":true,"DFEE1B":true,"DFEE1E":true,"DFEE1F":true,"DFEE20":true,"DFEE21":true,"DFEE22":true,"DFEE23":true,"DFEE24":true,"DFEE25":true,"DFEE26":true,"DFEE27":true,"DFEF1E":true,"DFEF1F":true,"DFEF20":true,"DFEF21":true,"DFEF22":true,"DFEF23":true,"DFEF24":true,"DFEF25":true,"DFEF26":true,"DFEF27":true,"DFEF28":true,"DFEF2C":true,"DFEF2D":true,"DFEF2E":true,"DFEF2F":true,"DFEF30":true,"DFEF31":true,"DFEF32":true,"DFEF33":true,"DFEF34":true,"DFEF35":true,"DFEF36":true,"DFEF37":true,"DFEF38":true,"DFEF39":true,"DFEF3A":true,"DFEF3B":true,"DFEF40":true,"DFEF41":true,"DFEF42":true,"DFEF43":true,"DFEF4B":true,"DFEF4C":true,"DFEF4D":true,"DFEF59":true,"DFEF5A":true,"DFEF5B":true,"DFEF5C":true,"DFEF5D":true,"DFEF5E":true,"DFEF5F":true,"DFEF60":true,"DFEF61":true,"DFEF62":true,"FF60":true,"FF62":true,"FF63":true,"FF69":true,"FF70":true,"FF71":true,"FF72":true,"FF73":true,"FF74":true,"FF75":true,"FF76":true,"FF77":true,"FF78":true,"FF79":true,"FF7A":true,"FF7B":true,"FF7C":true,"FF7D":true,"FF8101":true,"FF8102":true,"FF8103":true,"FF8104":true,"FF8105":true,"FF8106":true,"FFE0":true,"FFE1":true,"FFE2":true,"FFE3":true,"FFE4":true,"FFE5":true,"FFE6":true,"FFE7":true,"FFE8":true,"FFE9":true,"FFEA":true,"FFEE01":true,"FFEE02":true,"FFEE03":true,"FFEE04":true,"FFEE05":true,"FFEE06":true,"FFEE07":true,"FFEE08":true,"FFEE0A":true,"FFEE0B":true,"FFEE0C":true,"FFEE10":true,"FFEE11":true,"FFEE12":true,"FFEE13":true,"FFEE14":true,"FFEE1C":true,"FFEE1D":true,"FFF0":true,"FFF1":true,"FFF2":true,"FFF3":true,"FFF4":true,"FFF5":true,"FFF6":true,"FFF7":true,"FFF8":true,"FFF9":true,"FFFA":true,"FFFB":true,"FFFC":true,"FFFD":true,"FFFE":true,"FFFF":true};

// 'data' should look like "95050010203000..." etc.
// In other words: TLVs, serialized, as one big string.
// A TLV object is returned. Use it to look up Values by Tag name.
// TLV['95'] will contain the value of tag 95.
// TLV['9F26'] will contain the value of tag 9F26, etc.
function parseTags( data ) {

	var TLV = {}; // results go here
	
	// inner method
	function readData( amt, tag ) {

			data = data.slice( amt ); // get past tag bytes
			
			// find the Length (the L in TLV)
			var length = data.slice(0,2);  // read two nibbles
			data = data.slice(2); // get past those nibbles
			length = 1 * ("0x" + length);  // cast to Number
			
			if (length & 0x80) {  // high bit set? (EMV Book 3 Annex B2)
				highBitsByteExisted = 1;
				var lengthOfLength = length & 0x1F;
				lengthOfLength *= 2; // convert to nibbles!
				length = data.slice(0, lengthOfLength);
				data = data.slice( lengthOfLength ); // get past actual length
				length = 1 * ("0x" + length);  // cast to Number			
			}			
			
			length *= 2;  // number of nibbles of data to read

			var V = data.slice(0, length);

			// push the V onto the TLV array
			TLV[tag] = V;

			// if it was a constructed tag (FFEE01, e.g.) recurse:
			if ( tag.slice(0,2) == 'FF' ) {
				tmptlv = parseTags( V );
				for (var t in tmptlv)
					TLV[t] = tmptlv[t];
			}
			data = data.slice( length ); // get past the data
		}
	
	var THE_SUN_SHINES = 1;
	var amtRead,tag;

	while( THE_SUN_SHINES ) {

		if ( data == "") break; // loop ends
		
                for ( amtRead = 2; amtRead <= 6; amtRead += 2 )      
		    if ( (tag = data.slice(0,amtRead).toUpperCase() ) in _KnownTags )
                break;
		    else if ( amtRead == 6 ) {  // no known tag?
			data = data.slice( 2 );  // no tag found; just advance 2 chars
			console.log( "Expected a tag, found none. Data: \n" + data );
		    } // if not 3-byte

		readData( amtRead, tag ); // This method shortens data (by amtRead) each time	
	}  // while loop
	
	return TLV;	 // return an object in which TLV[ key ] == V
}

 

The tactic we use here is brain-dead simple:

First, make available a big dictionary of tag identifiers, containing all known EMVCo (industry standard) tags, plus all known ID TECH proprietary tags. We call this dictionary _KnownTags, and you can test an identifier like '5A' for existence by seeing if _KnownTags[ '5A' ] returns true.

Next: Parse!

Our parsing algorithm is super simple:

Read two nibbles at a time into a tag variable, and test whether the tag exists in the dictionary. All tags in the dictionary will be one, two, or three bytes long, so if we read 6 nibbles without finding a known tag, just advance the reading frame by 2 nibbles and continue on like nothing happened (after emitting a console message saying "Expected a tag, found none"). If you want to be fussy and throw an exception here, you can, but my philosophy is that (depending, of course, on the circumstances) a parser should by default be fail-soft (fault tolerant), in case you still want to use the rest of the parsed data.

Once a tag is found, use a worker method, in this case an inner function called readData(), to read past the tag, read the Length, and use the Length to read the Value. (Here, we need to be careful to check the top bit of the presumed Length, to see whether we need to follow the length-of-the-Length extensibility hack  rule mentioned earlier.)

Put the Value into a storage object under a lookup key of tag.

At the end, return the storage object.

So let's try a real-world example. Suppose you've got an ID TECH Augusta chip-card reader, and you're using it in keyboard mode to capture Quick Chip data. The data that streams out of the device when you dip a card might look like:

DFEE25020002DFEE26022000DFEE120A62994900000000000074DFEF5D105128CCCCCCCC2877D1801622CCCCCCCC57189F7E8B5A206B4F2CEA931148704EC549EDBAB728643E9197DFEF5B085128CCCCCCCC28775A10B5DECD79E3D200A6DE66A20C18DE80AC5F201A2F434849502054455354204341524420202020202020202020205F24031801315F25031501015F280208405F2A0208405F2D02656E5F3401005F57010050104465626974204D6173746572436172644F07A0000000041010820239008407A00000000410108C219F02069F03069F1A0295055F2A029A039C019F37049F35019F45029F4C089F34038D0C910A8A0295059F37049F4C088E1200000000000000004203440341031E031F039C01009F02060000000000009F03060000000000009F10120110200005620400000000000000000000FF9F13009F20009F2608C837A85C5DFE75739F2701009F34031E03009F360202669F3704BB8050C99F38009F3901079F4D009F4F00950504000000009B02E8008A025A3399009F5B00DFEF4C06002100000000DFEF4D28AA839B4B402083DDEC00614D1703B139A07586453583B4A03AB333FB210FD1CD4F8AC3603D75688E

 

This is a big block of TLV data that begins with an ID TECH proprietary tag of DFEE25. (You can learn more about what ID TECH's tags mean by downloading the ID TECH TLV Tag Reference Guide from https://atlassian.idtechproducts.com/confluence/display/KB/Downloads+-+Home.) Most of the tags in this block, however, are industry-standard EMVCo tags. If we assign the block, as a string, to a JS variable called tagblock, and then load the above parser and run it with parseTags( tagblock ), we'll get back an object with tags and values, like this:

 
50: 4465626974204D617374657243617264
57: 9F7E8B5A206B4F2CEA931148704EC549EDBAB728643E9197
82: 3900
84: A0000000041010
95: 0400000000
99:
DFEE25: 0002
DFEE26: 2000
DFEE12: 62994900000000000074
DFEF5D: 5128CCCCCCCC2877D1801622CCCCCCCC
DFEF5B: 5128CCCCCCCC2877
5A: B5DECD79E3D200A6DE66A20C18DE80AC
5F20: 2F43484950205445535420434152442020202020202020202020
5F24: 180131
5F25: 150101
5F28: 0840
5F2A: 0840
5F2D: 656E
5F34: 00
5F57: 00
4F: A0000000041010
8C: 9F02069F03069F1A0295055F2A029A039C019F37049F35019F45029F4C089F3403
8D: 910A8A0295059F37049F4C08
8E: 00000000000000004203440341031E031F03
9C: 00
9F02: 000000000000
9F03: 000000000000
9F10: 0110200005620400000000000000000000FF
9F13:
9F20:
9F26: C837A85C5DFE7573
9F27: 00
9F34: 1E0300
9F36: 0266
9F37: BB8050C9
9F38:
9F39: 07
9F4D:
9F4F:
9B: E800
8A: 5A33
9F5B:
DFEF4C: 002100000000
DFEF4D: AA839B4B402083DDEC00614D1703B139A07586453583B4A03AB333FB210FD1CD4F8AC3603D75688E
 

Some of these tags are empty. Some (like 9F27) contain a Value of 00. Some are encrypted. But basically, you have all the tags you need, right here, to run an EMV transaction.

Why use JavaScript to do TLV parsing? Well, if I told you the real answer to that, I'd have to kill you be spoiling the suspense you're no doubt feeling right now if I hint around about ways to use Node.js in the payment-app environment, how to talk to credit-card readers using JavaScript, how to hit back-end test servers using Servlets and AJAX, etc. All of which is coming up soon right here, so bookmark this blog and come back soon!

 

 

Hits: 1417
0

Posted by on in Blog posts

Augusta from ID TECHI've mentioned our Augusta (see photo) in previous posts, and I've mentioned that Augusta with Quick Chip and M/Chip Fast is patent-pending. But why? Why is it patent-pending? What the heck is patentable about it? Isn't Quick Chip in the public domain? (Yes, actually. Visa considered patenting it, but decided not to.) What is there about Augusta with Quick Chip and M/Chip Fast that's so darned innovative, anyway?

Short answer: The Big Innovation here is that Augusta is the only chip-card reader on the market that can do EMV entirely in keyboard mode. Which means it can be adapted for use in browser apps: that is, virtual terminal apps that slurp character data from a reader.

It means you can talk to a chip card -- and get ordinary ASCII data back.

That's a Very Big Deal.

It's not hard to find magstripe readers that operate entirely in keyboard mode. You know the kind of readers I'm talking about: the kind where you plug the unit into a laptop or tablet, swipe a card, and then track data magically appears onscreen wherever the cursor is. That's keyboard mode. It works because the card reader is a serial device tricks the host computer into thinking the device is actually a keyboard!

But a chip card reader is a different story. 

Most chip card readers require special software to operate, because the host application needs to talk to the chip on the card (right? that's the whole point!), but the chip only speaks binary. In fact, the conversation between the host app and the chip is pretty low-level bitsy-bytesy stuff, involving cryptograms and special handshakes and event notifications (and other things) that keep the host computer tied up in knots for ten, twenty seconds before you're prompted to "please remove your card."

Until Augusta came along, talking to a chip card was pretty heavy-duty stuff requiring extraordinarily intricate, computation-intensive software on the host computer (or on the electronic cash register). With Augusta, no special software is required to get data out of the card. After you dip a card, the reader outputs ASCII keystroke data (on its own, without programmer intervention); data of a kind that can be consumed by any app that expects to see text. You can literally open Notepad on a Windows computer, plug Augusta into a USB port, dip a card, and watch chip-card data appear in Notepad, as text. This is what sets Augusta apart. This is what's patentable. This is what you won't find anywhere else on the market.

What does the data look like? Well, as you may (or may not) know, EMV transactions result in TLV data. TLV means Tag, Length, Value. It's a way of representing data in easy-to-parse form. So for example, one of the TLVs you get back from Augusta after dipping your card may look like 5F 24 03 18 01 31. The "5F24" part is the Tag. This is an EMVCo-defined standard industry tag for Expiration Date. The '03' after the tag is the Length: We have three bytes of data. The data bytes are 18 01 31. This means the card expiration date (YY-MM-DD) is January 31, 2018.

With Augusta, an EMV transaction results in a text stream containing TLV data. The exact data will (obviously) depend on the circumstances, but in most cases you'll get back something like 40 TLVs, including all the stuff you need for a receipt (such as masked track data, in tag DFEF5D; expire data in tag 5F24; cardholder name, in tag 5F20; and so on) plus encrypted PAN (tag 5A), cryptogram information data (9F27), and lots more. An actual data block looks like this (with spaces inserted between byte values, for clarity); notice tags are in blue, lengths in orange, data in brown.

DF EE 25 02 00 02 DF EE 26 02 20 00 DF EE 12 0A 62 99 49 00 00 00 00 00 00 63 DF EF 5D 10 51 28 CC CC CC CC 28 77 D1 80 16 22 CC CC CC CC 57 18 03 8C CC 33 FB 1D 32 99 5F B6 F8 65 EA FE 54 69 90 55 A6 BC 18 A2 0D 50 DF EF 5B 08 51 28 CC CC CC CC 28 77 5A 10 44 B6 48 D3 53 D0 4B E1 DB DB B7 56 5D 4F D0 21 5F 20 1A 2F 43 48 49 50 20 54 45 53 54 20 43 41 52 44 20 20 20 20 20 20 20 20 20 20 20 5F 24 03 18 01 31 5F 25 03 15 01 01 5F 28 02 08 40 5F 2A 02 08 40 5F 2D 02 65 6E 5F 34 01 00 5F 57 01 00 50 10 44 65 62 69 74 20 4D 61 73 74 65 72 43 61 72 64 4F 07 A0 00 00 00 04 10 10 82 02 39 00 84 07 A0 00 00 00 04 10 10 8C 21 9F 02 06 9F 03 06 9F 1A 02 95 05 5F 2A 02 9A 03 9C 01 9F 37 04 9F 35 01 9F 45 02 9F 4C 08 9F 34 03 8D 0C 91 0A 8A 02 95 05 9F 37 04 9F 4C 08 8E 12 00 00 00 00 00 00 00 00 42 03 44 03 41 03 1E 03 1F 03 9C 01 00 9F 02 06 00 00 00 00 00 00 9F 03 06 00 00 00 00 00 00 9F 10 12 01 10 20 00 05 62 04 00 00 00 00 00 00 00 00 00 00 FF 9F 13 00 9F 20 00 9F 26 08 D2 EC AA C1 36 04 A7 22 9F 27 01 00 9F 34 03 1E 03 00 9F 36 02 01 A9 9F 37 04 9A 4D 21 88 9F 38 00 9F 39 01 07 9F 4D 00 9F 4F 00 95 05 04 00 00 00 00 9B 02 E8 00 8A 02 5A 33 99 00 9F 5B 00 

Your virtual terminal app will filter this data as appropriate, display some of it on the screen, and send the rest to the gateway or back end for processing. 

With most chip-card readers, developers have to go to a lot of trouble to obtain exactly the TLVs they need, at exactly the right points in the transaction, but with Augusta, all the data you need is there, in one character stream, all at once; you can slurp it into a web page with ordinary JavaScript keypress event handlers. Then process as you wish. You don't have to open a USB connection with the reader (it's a driverless reader!); you don't have to issue firmware commands; you don't have to orchestrate complex interactions with the card. Augusta's certified L2 Common Kernel handles all the hard stuff for you, then hands you the data, all at once, at the end: This is what Quick Chip and M/Chip Fast allows you to do. It's a style of EMV interaction that takes no work on the developer's part. (Okay, that's a bit of an exaggeration. It takes a little work. But nothing like regular EMV!)

For the merchant, it's a dream come true, because you just plug Augusta in, fire up the web browser, and you're ready to rumble. No special software installation needed.

So if you're a payment app developer, system integrator, ISV, or virtual-terminal wonk who needs to get EMV up and running quickly, there is literally no better, easier way to get started than with the patent-pending Augusta from ID TECH. It's the easy way to do EMV -- with a keyboard device that doesn't demand any special handshakes.

Want to find out more? We've got all the wonky details in our white paper, which is free for immediate downloading (no registration required) right here. Or call us at the following number and request a demo unit:

1-800-984-1010

Naturally, Augusta supports your choice of AES or TDES encryption (or no encryption!), or FPE, with DUKPT key management. There's even an SRED version, if you want it. And be sure to check out our short video! Also check out all of the downloadable Augusta resources on our Knowledge Base.

Hits: 1031
0

Posted by on in Blog posts

b2ap3_thumbnail_augusta.jpg

At ID TECH, we're excited about Faster EMV (aka Quick Chip) technology. 

You should be, too!

In case you haven't heard, Faster EMV (originally a Visa innovation called Quick Chip, but now officially blessed by all major card issuers) has the potential to turn a 20-second chip card transaction into a super-fast (2-second) "dip and go" scenario. It's standard EMV, hard-wired for speed. And it's easy to implement; we've done all the hard work for you.

We think it has the potential to change the outlook for EMV in the U.S. 

But note well, this is not just about faster transactions (and the resulting better customer experience); it's also about making EMV technology merchant-friendly, by letting chip-card readers interoperate with browser-based point-of-sale software, something that hasn't been possible with chip-card readers until now.

Recall that in the U.S., we have a strange situation in which 80% of credit cards now have a chip, but less than a third of merchants have chip-capable readers that are turned on and functional. All of the largest retailers (and ATMs) can process chip-card transactions, but there are still literally millions of smaller (and midsize) merchants, in the U.S., who either can't or won't upgrade to a fully functional EMV reader. What many of these merchants are waiting for is an ultra-low-cost, EMV-certified, encryption-enabled chip-card reader that integrates easily with phone, tablet, or PC-based POS systems. 

That's exactly what the patent-pending Augusta with Quick Chip is (see photo).

For under $150, you have an encryption-enabled EMV L2 chip-card reader that can run a transaction in two seconds (or, alternatively, fall back to magstripe) while also outputting EMV data as a character stream, in "keyboard mode." The latter is important, because "keyboard mode" character data is what many browser-based POS systems require; and yet, to date, no chip-card (EMV) reader, as far as we know, has been designed to operate in this mode (except for the patent-pending Augusta). 

What it means for ISVs, payment-app developers, and POS-system integrators is that there is now, finally, an inexpensive EMV card reader that can be integrated into "virtual terminal" scenarios in literally a day or two, using JavaScript and browser technology (and driverless USB). Plug the Augusta in, intercept its character stream (TLVs come across the wire as "keyboard data"), do whatever preprocessing you want to do in JavaScript, send the resulting data off to your favorite gateway, acquirer, or back end; and you're done. There's no need to develop USB drivers, orchestrate complex I/O between devices, send raw firmware commands to the reader, or anything like that. Just slurp a character stream. It's ASCII!

What it means for merchants is that a POS system with EMV capability doesn't have to be a complex, expensive proposition. It simply means plugging an inexpensive, driverless card reader into the USB port of a tablet or PC, and loading a POS app on a web browser.

What it means for customers is that a chip-card transaction doesn't have to be the insert-your-card, tap-your-foot, look-at-your-watch, stare-at-the-ceiling experience it so often tends to be at big-box retailers. It can be as simple as dipping your card and putting it away two seconds later while a receipt prints out. 

In short: It's a win-win-win for ISVs, merchants, and customers.

For more, be sure to see our Augusta with Quick Chip white paper, a free, no-registration-required (in a word: quick) download, available here. Or call ID TECH to request a demo unit:

1-800-984-1010

 And yes, Augusta is available in an SRED (Secure Read and Exchange of Data) version, as well, for very little additional cost. Ask about it, if you're putting together an end-to-end-encrypted solution. (Note: Non-SRED "standard" Augusta also supports fulltime encryption, but doesn't have the special tamper mitigation and other high-end security features required for full PCI-PTS/SRED certification.)

Tagged in: Augusta EMV Quick Chip
Hits: 839
0

Wouldn't it be great if a chip-card transaction took, say, 2.0 seconds instead of 20 seconds?

We thought so too. Which is why ID TECH is proud to introduce the patent-pending Augusta with Quick Chip (see photo), the fastest contact-EMV card reader money will buy. Just insert your chip card, remove it two seconds later, and put your card away while a receipt prints out. It's that fast. (Come see it in person at the National Retail Federation show, Booth 4521, at the Javits Center, Jan. 15, 16, and 17.)

Augusta is blazing fast, but this isn't just about speed. It's also about making EMV play nice with virtual terminal technology (browser-based point-of-sale apps), and that turns out to be a much harder nut to crack.

But crack it we did! b2ap3_thumbnail_Augusta.png

Old-school magstripe readers lend themselves nicely to browser-based POS apps, because you can just swipe a card through the reader, and track data will show up as a raw stream of characters (i.e., "keyboard data") in a text field on the screen, capturing the customer's data right where and when it's needed.  That's great for doing old-fashioned magstripe transactions, but unfortunately, chip-card readers can't output raw "magstripe data" straight to the screen. The data inside a chip card isn't meant to be captured this way, and that's part of what's keeping small merchants from adopting chip card technology. 

In a standard chip-card transaction, there's a lot of "chatter" between the card, the reader, and the capturing POS app. The reader interrogates the card, the POS interrogates the reader, and lots of data has to be passed back and forth in order for the POS to know when (and whether) to go online for authorization. Even after the POS goes online, the card is allowed to review the online decision (and in theory, it can veto the transaction). All of this happens while the card remains inserted in the machine. The whole process can easily take 10 to 20 seconds. 

With the new, patent-pending Augusta card reader, ID TECH changes the game by combining lightning-fast Quick Chip technology with USB "keyboard mode" output. Unlike other chip card readers, Augusta operates in USB-KB mode, capturing EMV data as ASCII-encoded TLV triplets (tag, length, value), which can appear directly in the POS app, or be intercepted and processed via JavaScript using standard browser event handlers. Instead of a weeks- or months-long EMV integration project, ISVs and payment-app developers can get Augusta integrated into a virtual-terminal environment in an afternoon. And certification is quick, due to the super-simplified nature of the Quick Chip certification process.

In Quick Chip mode, much of the EMV transaction logic is hard-coded, so that once the card has been read, the customer can remove the card from the machine immediately. The "online authorization" happens while the customer is putting his or her card away. The transaction takes just a couple of seconds (about the same amount of time as a magstripe transaction), greatly streamlining the customer experience.

The details of how Quick Chip works are pretty geeky, but if you want to see a detailed technical explanation, ID TECH has a nice white paper on this subject. It's free for downloading and (for the time being) requires no registration whatsoever, so head here to get the PDF. And in the meantime, if you have questions about the product or the technology, give ID TECH a call at 1-800-984-1010, or drop by Booth 4521 at the National Retail Federation show at the Javits Center in New York, Jan. 15, 16, or 17, where you can see a live demo of the Augusta, in person -- and find out for yourself just how fast a chip-card transaction can be.

 

For More Information

Check out our Augusta product page. Or call us at the number below, any time:

1-800-984-1010

Be sure to ask about the SRED version of Augusta, too. 

If you enjoyed this blog (or the white paper), by all means sign up for monthly updates (see link at top of page). 

Tagged in: Augusta EMV Quick Chip SRED
Hits: 1494
0

Posted by on in Blog posts

Until recently, reading credit card data was as easy as swiping a card through a reader (such as one of the many readers made by ID TECH) and having a virtual-terminal app (or other app) slurp up the track data as it comes straight out of the reader. The data in question would simply show up as keystrokes on a screen, unencrypted.

Those were the days! b2ap3_thumbnail_DSC00843.jpg

Suffice it to say, things have changed. Today, magstripe readers generally output encrypted data, over USB (often in HID mode, rather than  keyboard mode), and most card readers today have to handle chip cards in addition to magnetic-stripe cards. Chip card data takes the form of TLVs (tags, length, values) and can look quite a bit different from the "plain old magstripe data" of years past. Also, it's invariably encrypted.

In recent posts, I showed how to decrypt credit card data using industry-standard TDES and AES decryption algorithms in conjunction with industry-standard (ANSI X9.24) DUKPT key derivation techniques, but we didn't talk about how to obtain parsed data in the first place. What does "credit card data" look like now? How can you obtain it and parse it? How do you know which parts are encrypted?

The answer to the first question (what does credit card data look like?) varies a lot, depending not only on the make and model of the card-reading equipment you're using, but on whether the transaction in question was done via mag swipe, dip (contact EMV), or contactless/NFC interaction. In general, you're doing a lot more than just reading raw track data. You're also obtaining a KSN (Key Serial Number), which is needed for decryption, and harvesting various kinds of metadata pertinent to the transaction. It's true, you might only be interested in obtaining (say) raw Track 2 data, but in the process of obtaining it, you're going to have to deal with lots of other data, too.

Let's take a quick look at a real-world example using a Starbucks gift card given to me by a guilt-ridden barista as a makegood after unexpectedly running out of Mesopotamian Kumquat-Absinthe Latte. If we swipe the Starbucks card through ID TECH's Augusta card reader operating in keyboard mode, with Notepad window open (and the cursor in the text window), we get the following data in Notepad:

 

02ED01801F4C2800839B%*6010********8765^0254/SERVICERECOVERYUSD^*******************************?*;6010********8765=********************?*95025C86987E4F7DD07D58730EB79FDFB90AB7F23E6ECA6F4F04A67BF511EE13F950903BDE77624680C460E9C36C4F9136256BB93A38CB98F95626DCFAF9335CE0A213074CC1CD84CC911398E06756C464AB036B694228ADA7EC018F495A013AF8A04C976288FE2F80271E6E53D987DE19ACA2707BFF2C78000000000000000000000000000000000000000000000000000000000000000000000000000000003631335435333536313862994900750002A00308108E03

 

This is a lot more than "raw track data." You can recognize the masked track data (which begins with B% and contains many asterisks, finally ending in ?*), but that conceals the Primary Account Number (PAN), which is actually encrypted. Most of what you're seeing here is a hexadecimal representation of the binary data coming out of the reader.

Parsing this big block of stuff is easy, if you know how. The fastest  way to parse it is to run the data through ID TECH's free Parsomatic tool, which is an HTML form that can render all the pieces of data in an intelligible fashion.

Every card reader has its own proprietary way of representing card data. ID TECH represents magstripe data in a format known as Enhanced Encrypted MSR format. The format includes 26 fields of data; all 26 fields are described in detail in document P/N 80000502-001, ID TECH Encrypted Data Output.

To give you an idea of what's on the card, let's consider the first 5 bytes of data (02 ED 01 80 1F). According to Parsomatic, these 5 bytes contain the following information:

STX 02
LENGTH ED 01
Card Encode Type 80
Track Status (1F)
0-------  0 Reserved for future use
-0------ 1: Field 10 optional bytes length exists (0: No Field 10)
--0----- 1: Track 3 sampling data exists (0: Track 3 sampling data does not exist)
---1---- 1: Track 2 sampling data exists (0: Track 2 sampling data does not exist)
----1--- 1: Track 1 sampling data exists (0: Track 1 sampling data does not exist)
-----1-- 1: Track 3 decode success (0: Track 3 decode fail)
------1- 1: Track 2 decode success (0: Track 2 decode fail)
-------1 1: Track 1 decode success (0: Track 1 decode fail)

The first byte (02) is simply STX, the "start" byte. The next two bytes (ED 01) represent the length, in hex, of the overall data payload (little-endian: ED 01 actually means 0x01ED, or 493 bytes of data). The Card Encode type is 0x80, which (in English) means our reader considers this is a financial card. Track Status (value: 0x1F) is a status byte containing eight bit flags, to let you know which tracks were present on the magnetic stripe (there can be up to 3) and which ones were read successfully. In this case, all 3 physical tracks were read successfully, but data exists only on tracks 1 and 2.

Let's quickly look at the next 5 bytes. According to Parsomatic, those bytes, and their meanings, are as follows:

Track 1 Length 4C
Track 2 Length 28
Track 3 Length 00
Clear/Mask Data Sent Status (83)
1-------  Bit 7: 1   Serial Number present; 0 not present
-0------ Bit 6: 1 PIN Encryption Key; 0 Data Encryption Key
--0----- Bit 5: 1 Chip present on card. (First byte of service code was '2' or '6'.) Use EMV transaction if possible.
---0---- Bit 4: 0 TDES; 1 AES
----0--- Bit 3: 1 if fixed key; 0 DUKPT Key Management
-----0-- Bit 2: 1 if Track3 clear/mask data present
------1- Bit 1: 1 if Track2 clear/mask data present
-------1 Bit 0: 1 if Track1 clear/mask data present
Encrypted/Hash Data Sent Status (9B)
1-------  Bit 7: if 1, KSN present
-0------ Bit 6: if 1, session ID present
--0----- Bit 5: if 1, track3 hash data (SHA digest) present
---1---- Bit 4: if 1, track2 hash data (SHA digest) present
----1--- Bit 3: if 1, track1 hash data (SHA digest) present
-----0-- Bit 2: if 1, track3 encrypted data present
------1- Bit 1: if 1, track2 encrypted data present
-------1 Bit 0: if 1, track1 encrypted data present

On a financial card, Track 1 can be up to 79 bytes long; Track 2 can be up to 40 bytes long; and Track 3 can be up to 107 bytes. In this case, we've got 76 bytes (hex 0x4C) of data for Track 1 and 40 bytes (0x28) of data for Track 2. (Track 3 had zero bytes.) These lengths are important to know, not only for parsing out the masked track data but for determining the lengths of the encrypted versions of the tracks. The encrypted length is different than the actual native track length, because track data must be padded to a final length that's a multiple of 8 for TDES encryption, or a multiple of 16 for AES.

How do we know if the data is TDES-encrypted versus AES-encrypted? That information is in bit 4 of the Clear/Mask Data Sent Status byte (as shown above). There's also an Encrypted/Hash Data Sent status byte (shown above) to let you know whether encrypted track data (and validation hashes) are present, and whether a KSN exists.

Now we get to Track 1 and Track 2 masked data, followed by the encrypted versions of those, followed by some hash data (in this case, all zeros), and some other data (described below). Note that Parsomatic has converted the ASCII track data to a hex representation below:

Track1 Data 25 2A 36 30 31 30 2A 2A 2A 2A 2A 2A 2A 2A 38 37 36 35 5E 30 32 35 34 2F 53 45 52 56 49 43 45 52 45 43 4F 56 45 52 59 55 53 44 5E 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 3F 2A
Track2 Data 3B 36 30 31 30 2A 2A 2A 2A 2A 2A 2A 2A 38 37 36 35 3D 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 3F 2A
Track1 Encrypted Data 95 02 5C 86 98 7E 4F 7D D0 7D 58 73 0E B7 9F DF B9 0A B7 F2 3E 6E CA 6F 4F 04 A6 7B F5 11 EE 13 F9 50 90 3B DE 77 62 46 80 C4 60 E9 C3 6C 4F 91 36 25 6B B9 3A 38 CB 98 F9 56 26 DC FA F9 33 5C E0 A2 13 07 4C C1 CD 84 CC 91 13 98 E0 67 56 C4   Decrypt this data
Track2 Encrypted Data 64 AB 03 6B 69 42 28 AD A7 EC 01 8F 49 5A 01 3A F8 A0 4C 97 62 88 FE 2F 80 27 1E 6E 53 D9 87 DE 19 AC A2 70 7B FF 2C 78   Decrypt this data
Track 1 Hashed 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Track 2 Hashed 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Reader Serial Number 36 31 33 54 35 33 35 36 31 38
KSN 62 99 49 00 75 00 02 A0 03 08
LRC 10
Checksum 8E
ETX 03

 

Notice that Parsomatic inserts a "Decrypt this data" link next to encrypted data (see above). If you click the link, it takes you to ID TECH's Encrypt/Decrypt Tool, where the data in question can be viewed in decrypted form. 

The final fields of data include the Reader Serial Number, the Key Serial Number (KSN), an LRC, a checksum, and ETX (end of transmission). The LRC is simply a byte value representing the XOR (exclusive OR) of all payload data bytes, while the checksum is a one-byte arithmetic sum (neglecting overflow, obviously) of all bytes in the payload. The LRC and checksum can be used to check the integrity of the data payload. (They're somewhat easier and quicker to calculate than a proper CRC.)

So as you can see, there's a lot more to a credit card swipe than "track data." We haven't even touched on the significance of some of the data fields (maybe we can go there in a future post). Nor have we said anything about EMV data (which is definitely going to take another post). But this should get you started, if you're trying to parse data coming out of an ID TECH credit card reader. 

For more information, be sure (as I said earlier) to consult our technical documentation on the Enhanced Encrypted MSR data format: ID TECH Encrypted Data Output

Tagged in: Augusta ID TECH KSN MSR TDES
Hits: 2323
0

Posted by on in Blog posts

Customers often ask: How can I decrypt the data coming out of my ID TECH credit-card reader? b2ap3_thumbnail_photodune-2692443-unlocked-card-02-xs-300x225.jpg

The answer: You need to know the algorithm that was used to encrypt the data, and the key that was used. Then you can decrypt the data using the key.

These days, almost all credit-card data gets encrypted using a one-time-only key, obtained via a special key-management scheme called DUKPT (which stands for Derived Unique Key Per Transaction). It's important to understand that in the DUKPT world, every transaction has its own key. The key can't be reused for any other transaction(s); hence, replay attacks are impossible.

The question is: How can you derive a DUKPT key that will unlock a given transaction? The answer is: Generally speaking, you need the Key Serial Number (KSN) for the transaction, plus a special value called the IPEK, or initial key that was injected into the credit card reader. The IPEK, in turn, is derived from a super-secret key (that's never injected into a card reader) called the BDK (Base Derivation Key). Unlike the BDK, the IPEK is unique to a given physical device. (One BDK can be the source of many unique IPEKs.) If you don't know the IPEK for your device (and there's no reason why you would, since the IPEK is never written down anywhere), you can derive it from a KSN and a Base Derivation Key, using the technique described in Part I of this article.

Deriving a session key (sometimes called a working key, or simply "data key") is actually best thought of as a 3-step process. The steps are:

1. Use the BDK and KSN to derive the IPEK. (See Part I of this article for details on how to do this.)

2. Use the ANSI X9.24 (DUKPT) key-derivation algorithm to derive a basis key, or initial "derived key," from the KSN and IPEK.

3. Convert the derived key of Step 2 into your choice of Data Key, PIN Key, or MAC Key. (Note that while most credit card readers are set up to use the Data variant key for the transaction session key, some are, in fact, set up to use the PIN variant instead.)

Let's go ahead and see what's involved in obtaining the "derived key" (step 2), since this is by far the most painstaking part of the 3-step process. Once we've got the derived key, we'll talk about how to transform it into a Data, PIN, or MAC variant, which is relatively easy.

We'll be using a fair amount of pseudocode below, but rest assured, you can find complete, working source code (in JavaScript) for all of the following steps in our popular Encrypt/Decrypt Tool. (Try it now, if you haven't already. It's a self-contained web page that works in any modern browser.)

 

Deriving a Key

To derive the basis key from which a Data, PIN, or MAC variant can be created, you need to start with a transaction KSN and an IPEK. Once you have those (again: see Part I of this series), do this:

1. Obtain the bottom (rightmost) 8 bytes of your 10-byte KSN. Discard the top two bytes. 

2. Create a BaseKSN variable to hold a masked version of your 8-byte KSN. Obtain the masked version by ANDing the 8-byte KSN of Step 1 with the (hex) value 0xFFFFFFFFFFE00000.

3. Get the counter bits from your original (not masked!) 10-byte KSN by ANDing its bottom three bytes with 0x1FFFFF. (Recall that the bottom 21 bits of a KSN comprise the transaction counter.) We'll put this in a variable called (what else?) counter.

4. Copy your 16-byte IPEK into a variable called curKey.

5. Now we need to set up a loop. Each time through the loop, we are going to inspect the counter bits (starting from the top bit, or 21st bit; on the second pass through the loop, we'll check the 20th bit; then the 19th bit; and so on). Each time we find a bit that's turned on, we will OR it into the BaseKSN, then call generateKey() to update curKey. The BaseKSN will accumulate bits with each trip through the loop and the curKey value will update with each turned-on counter bit we find.

for (var shiftReg = 0x100000; shiftReg > 0; shiftReg >>= 1)
        if ( ( shiftReg & counter ) > 0 )  {
	baseKSN |= shiftReg;
	curKey = generateKey( curKey, BaseKSN ); 
}

What does generateKey() do? Glad you asked! If your programming language supports BigInteger math, the code will look something like this:

function generateKey(key, ksn) {

    var mask = 0xC0C0C0C000000000C0C0C0C000000000;
    var maskedKey =  mask ^ key;

    var left  = encryptRegister( maskedKey, ksn );
    var right = encryptRegister( key, ksn );

    return (left << 64) | right; // join left and right together
}

Okay. You can see that the 16-byte key is masked, then used to encrypt the 8-byte ksn value, to get the left half (the left 8 bytes) of a new key. The right half of the new key is a cipher created from the same ksn, but using an unmasked key.

Finally, you need to know what encryptRegister() looks like. This is it:

// Returns an 8-byte result
function encryptRegister(key, ksn) {

	var CBC = 1;  		     // cipher block chaining enabled
	var iv = "\0\0\0\0\0\0\0\0"; // initial vector

 	var bottom8 = key & 0xFFFFFFFFFFFFFFFF; // bottom 8 bytes

	var top8 = (key &  0xFFFFFFFFFFFFFFFF0000000000000000) >> 64; // top 8 bytes

	var bottom8xorKSN = bottom8 ^ ksn;

	// This will be single-DES because of the 8-byte key:
	var desEncrypted = des( top8, 
			    bottom8xorKSN, 
			    true, /* encrypt */
			    CBC,
			    iv );

	var result = bottom8 ^ desEncrypted;
	
	return result; // return the 8-byte result
}

Note that Cipher Block Chaining is actually meaningless here, because we are encrypting an 8-byte value (one block of data). There is nothing to "chain." It's included in the code simply because the encryption routine happens to require a parameter that says yes or no to chaining.

Also note that we are using an 8-byte key to do the encryption. TDES defaults to single-DES when the key is only 8 bytes long. That's because an 8-byte key would (in triple DES) result in an encrypt/decrypt/encrypt cycle that's equivalent to doing a single encrypt. 

The plain-English explanation of what's going on is that the routine uses the top 8 bytes of a 16-byte key to encrypt a special value that comes from XORing the bottom 8 bytes of the key with the (8-byte) ksn. The result is a one-way hash of the ksn.

Put it all together, and the result is that the loop from Step 5 above produces a curKey value that ends up being a basis key from which we can derive Data, PIN, or MAC variants. (The loop in Step 5 is, or should be, part of a function that ends up returning curKey, which is the basis key.)

It's time now to look at those three "key variant" options in more detail.

 

Creating Data, PIN, and MAC Key Variants

ANSI X9.24 allows a DUKPT key to take on one of three final forms, called variants. The forms are MAC, PIN, and Data. Let's defer any discussion of what these various key types are used for in order to concentrate on how they're created.

The starting point for any of the variants is a DUKPT basis key (the derived key that we called curKey in Step 5 further above). To get the MAC variant, you simply need to XOR the basis key (the "derived key") with a special constant:

MACkey = derivedKey ^ 0x000000000000FF00000000000000FF00;

The PIN variant, likewise, is created in similar fashion, but using a different constant:

PINkey = derivedKey ^ 0x00000000000000FF00000000000000FF;

The Data variant requires yet another constant:

Datakey = derivedKey ^ 0x0000000000FF00000000000000FF0000;

For MAC and PIN variants, the XOR operation constitutes the final step in creating the relevant session key. For the Data variant, it's customary to perform one additional step, involving a one-way hash (to preclude any possibility of someone back-transforming a Data key into a MAC key). In pseudocode:

    // left half:
    var left = des(  EDE3KeyExpand( derivedKey ),
			    top8bytes( derivedKey ), 
			    true,
			    CBC,
			    iv );     

    // right half:
    var right = des( EDE3KeyExpand( derivedKey ),
			    bottom8bytes( derivedKey ), 
			    true,
			    CBC,
			    iv );   

finalDataKey = (left << 64) | right;  // combine halves

In English: First, obtain a 24-byte version of your derived key, by using the EDE3 expansion method. (This simply means copying the first 8 bytes of a 16-byte key onto the tail end of the key, creating a 24-byte key in which the first and last 8 bytes are the same.) Use that key to TDES-encrypt the first 8 bytes of your 16-byte derived key, thereby creating an 8-byte cipher. That's the left half of the eventual data key. To create the right half, use the same 24-byte key to encrypt the bottom 8 bytes of the derivedKey. Combine the two 8-byte ciphers (left and right pieces), and you're done.

 

Known-Good Values

If you're trying to do this at home, you may want to check your work against some known-good values. So, start with a 16-byte BDK of 0123456789ABCDEFFEDCBA9876543210 (hex), which is the test-key value everyone tends to use. Try a test KSN value of 629949012C0000000003. These two values should allow you to derive an IPEK of D2943CCF80F42E88E23C12D1162FD547. (Refer to Part I of this article if you want to see how to derive the IPEK.)

Starting with the aforementioned IPEK, you should see the following values when deriving a "derived key" (or DUKPT basis key):

On the first trip through the "if" of the KSN-counter loop, your BaseKSN will be 49012C0000000002 and curKey will become B58CDA5C7A1E9FF5E7335B988626D01A after generateKey().

On the second trip through the "if" of the counter loop, you will have processed both "ON" bits of the counter, and therefore your BaseKSN will be 49012C0000000003 and the resulting curKey will be 841AB7B94ED086EBC2B8A8385DA7DFCA. (Remember, you are ORing counter bits, MSB first, into the BaseKSN. If the counter ends in 0x0F, the BaseKSN will go from 49012C0000000008 to 49012C000000000C to 49012C000000000E to 49012C000000000F as you OR bits successively.)

Your "derived key" will thus be 841AB7B94ED086EBC2B8A8385DA7DFCA.

After XORing the data-variant constant, the derived key will change to 841AB7B94E2F86EBC2B8A8385D58DFCA.

After enciphering the top and bottom halves of the latter value, using an EDE3-expansion key of 841AB7B94E2F86EBC2B8A8385D58DFCA841AB7B94E2F86EB, you should get a final data key of F739AEF595D3877F731782D28BB6AC4F. That is: Using the 24-byte EDE3 key value to encrypt 841AB7B94E2F86EB, you should get a cipher of F739AEF595D3877F, and using the same key to encrypt C2B8A8385D58DFCA, you should get a cipher of 731782D28BB6AC4F. Concatenate the ciphers, and you're done. You now have a 16-byte key with which you can decrypt data from the transaction whose KSN was 629949012C0000000003.

 

Example Code: The ID TECH Encrypt/Decrypt Tool

To see full source code for all of the DUKPT key derivation routines discussed here, be sure to download (and inspect the source code of) our HTML-and-JavaScript-based Encrypt/Decrypt Tool, which can calculate IPEKs, derive all 3 DUKPT key variants, encrypt or decrypt data using TDES or AES, and much more. You can use Chrome's excellent developer-console toolset to step through the Tool's code in real time, inspect variable values as they change, set breakpoints, etc. It's a tremendous learning aid. And it's free! So download the Encrypt/Decrypt Tool, play with it, and tell your friends about it. As far as I know, it's the only pure-JavaScript DUKPT implementation on the Web.

Hits: 2196
0

Posted by on in Blog posts

A question that comes up a lot is: The track data I'm getting from my ID TECH credit card reader is encrypted. How do I decrypt it?

The answer is: You need to obtain the proper session key for the transaction, and then use that key to decrypt the data payload via Triple-DES (or AES, as appropriate). b2ap3_thumbnail_security_padlock.jpg

The decryption process itself is gratifyingly anticlimactic. You'll likely use any of a number of existing open-source implementations of TDES or AES (there's no need to implement core crypto routines yourself), operating in CBC (Cipher Block Chaining) mode, with a default initial vector of all zero bytes. Assuming you have the correct 16-byte decryption key, the decryption process is easy.

The tricky part is deriving the key. For this, you'll need to understand ANSI X9.24-1, otherwise known as DUKPT.

 

Welcome to the World of DUKPT

It's important to understand that in credit card readers, each transaction produces data that will be encrypted using a different key. The key is unique to a given transaction (hence the acronym DUKPT: Derived Unique Key Per Transaction). No key is ever used twice. As a result, replay attacks are essentially impossible.

To understand how DUKPT works, you have to know a little bit about the concept of the Key Serial Number, or KSN. The main thing to know is that the KSN is a 10-byte value that changes for each transaction, since the bottom 21 bits comprise a counter. 

 

b2ap3_thumbnail_ksn.png

Key Serial Number layout. 

Remember: Every encrypted card transaction comes with a KSN. The KSN is always 10 bytes long. And it is always sent in the clear, since the KSN, by itself, discloses no sensitive information (yet is essential for deriving a session key).

When a card reader is configured for encryption at the factory, it gets injected with a 16-byte key and a 10-byte initial KSN. The key that's injected is derived from a super-secret key (that's never injected) called the BDK, or Base Derivation Key. (Note that because many keys can be derived from one BDK, it's possible, and in fact common, to inject hundreds or even thousands of card readers with unique keys that stem from a single BDK.) The derivation process, itself, requires the use of a KSN. Since the KSN includes information about the device's serial number (plus a lot of other miscellaneous "namespace" info), a hash (or key) produced from a given BDK+KSN combo will essentially be unique to the device. What's more, the original BDK can never be back-calculated from the hash (even if you know the KSN), because we're talking about a cryptographically secure one-way hash.

Whenever a transaction occurs, the card reader (if it supports DUKPT, as virtually all card readers these days do) generates a unique key from the current KSN value and from something called the IPEK (or Initial PIN Encryption Key). The resulting one-time-only session key is then used to encrypt sensitive portions of the transaction data. 

Once encrypted, transaction data is (or are) never decrypted again until reaching the authorized destination, which may be the card issuer. The party at the receiving end (e.g., the issuer) will use its own copy of your BDK (plus the transaction KSN) to re-derive the session key for the transaction, and recover the original (decrypted) transaction data. This is a so-called symmetric process, because both the encrypting party and the decrypting party must already know the same secret (the BDK). It's assumed that you will already have provided the receiving party with the necessary "secret" so that you can both decrypt messages.

 

The IPEK

The starting point for obtaining a DUKPT session key is always to derive the IPEK, or initial key, which you can do only if you know the original BDK and the KSN. (Here, any KSN from the device in question will work, since you're going to zero out the counter for this step.)

To derive an initial PIN encryption key (IPEK), you need to do the following:

1. If your BDK is 16 bytes in size, expand it to 24 bytes using the so-called EDE3 method. That simply means: copy the first 8 bytes of the key to the end of the key, creating a 24-byte key in which the first and last 8 bytes are the same.

If your original key (in hex) looks like this:

   0123456789ABCDEFFEDCBA9876543210

You want it to end up looking like this:

   0123456789ABCDEFFEDCBA98765432100123456789ABCDEF

2. Mask your 10-byte initial KSN by ANDing it against the hex value 0xFFFFFFFFFFFFFFE00000. We'll call the result the "masked KSN."

3. Create an 8-byte value out of the masked KSN by retaining only the first (i.e., leftmost) 8 bytes of the 10-byte masked KSN. Chop off the rightmost two bytes, in other words.

4. Using your expanded, 24-byte BDK as a key, TDES-encrypt the 8 bytes of masked KSN that you obtained in Step 3. You will use an initial vector of all zeros for this. (Note that cipher block chaining is not meaningful here, since the data in this case is just one block long: 8 bytes.) Retain the 8-byte cipher you obtain in this step, because it will become the left half of the 16-byte IPEK.

5. To get the right half of the IPEK, first XOR your original 16-byte BDK against the hex value 0xC0C0C0C000000000C0C0C0C000000000. (If you're using a programming language that supports big-integer math, this can be done in one line of code. If not, you'll need to XOR the two values incrementally, piece by piece.)

6. EDE3-expand the 16-byte value you obtained in Step 5, to obtain a 24-byte key value.

7. Using the 24-byte key value of Step 6, TDES-encrypt the 8 bytes of masked KSN that you obtained in Step 3. This is now the right half of the IPEK.

8. Concatenate the left and right halves of the IPEK. You now have the final 16-byte IPEK.

If you're implementing this in code yourself, try making an IPEK from a test key value of 0123456789ABCDEFFEDCBA9876543210 and a KSN of 62994900000000000001. The resulting IPEK should be B5610650EBC24CA3CACDD08DDAFE8CE3.

 

Key Management vs. Encryption Algorithms

You'll notice, by the way, that Triple-DES (TDES) is used a lot in DUKPT. At no time is AES ever used (even if your card reader is set up to use AES for encryption). The X9.24 standard calls for TDES, and sometimes plain DES. Get clear in your mind that the DUKPT key derivation process is entirely separate from the transaction-data encryption/decryption process. In one case, you're deriving a key. In the other case, you're using that key to do TDES or AES encoding. No encryption routine knows or cares where your key came from, or what algorithms were used in constructing it; the only thing that matters is that the key itself works. So while the data you need to unlock may well have been encrypted with AES, the key you use to unlock that data will be derived using DUKPT, which (internally) uses TDES.

 

Where's the Code?

In Part II of this post, we're going to go into considerable detail on how to use an IPEK plus a KSN to derive an actual DUKPT session key. We'll see actual source code so you can do the whole thing yourself. If you can't wait until next time to see the source code, go ahead and take a peek at our popular Encrypt/Decrypt Tool, which contains a fully functional JavaScript implementation of the DUKPT algorithms I'll be talking about in Part II (complete with open-source TDES and AES implementations). You can use the Encrypt/Decrypt Tool to derive DUKPT keys (in all 3 variants: PIN, Data, and MAC), encrypt or decrypt data (with TDES or AES), generate various kinds of hashes, and much more. Best of all, since the tool is just a web page, it will work in any browser (on any platform) that supports JavaScript.

Want to derive a data-variant DUKPT session key from a KSN and an IPEK? Continue to Part II of this article.

Tagged in: AES DUKPT encryption KSN TDES
Hits: 3236
0

Sometimes it can be extraordinarily handy to be able to communicate with a USB-HID device from your browser. Fortunately, Google Chrome offers exactly this capability (via JavaScript), and we can use it to talk to any of ID TECH's USB-basb2ap3_thumbnail_augusta-for-web.jpged credit card readers, on Windows, Mac OS X, or Linux. Let's look at how that's done.

For this exercise, I'm going to use ID TECH's popular dual-slot Augusta card reader (shown at right), which can read ordinary magstripe cards but also has full Level 1/Level 2 "contact EMV" capability. (And yes, you can carry out an encrypted chip-card transaction entirely in Chrome, using JavaScript, if you know how.) I'm going to use Google Chrome version 52.0.2743.116 running on Windows 7 Professional, although the code also works on Ubuntu 14.04 (providing your USB permissions are appropriately adjusted; see discussion further below). 

Support for connecting USB devices with Chrome is supplied by the chrome.hid API. This API is not available to regular browser pages, so don't try to use it from an ordinary HTML page. Instead, you need to package your HTML and scripts as a Chrome app. That's fairly simple to do.

All you need is the following zip file:

Augusta-Chrome-View.zip

Download and save the zip file anywhere on your local disk. Then do the following:

1. Unpack (extract) the zip file. It will create a folder containing 6 small files.

2. Launch your Chrome browser if it is not already running.

3. In Chrome, go to chrome://extensions/.

4. On the extensions page, check the Developer Mode checkbox in the upper right.

5. On the upper left, find the Load unpacked extension... button. Click it.

6. Navigate to the location of the folder from Step 1.

7. Select the folder and click OK.

8. The new extension should now be visible in the chrome://extensions/ page. Plug Augusta into a USB port. Then: Find the extension's Launch link (on your chrome://extensions/ page) and click it.

You should see a screen similar to the following:

 b2ap3_thumbnail_AugustaChromeViewScreenshot.png

Congratulations! You are now ready to use Augusta from Chrome. (If the device isn't shown as connected, unplug it and re-plug it back in.)

Select a command from the dropdown list. Then click the Send button. The command will be sent to Augusta over USB and you will see a response come back in the top text area.

How does all this work?

Glad you asked!

Physically, a Chrome app is just a folder containing half a dozen small files, two of which are icons (PNG images), three of which are script files, and one of which is HTML:

b2ap3_thumbnail_AugustaChromeFOLDERview.png

The background.js file is a text file containing the following text:

chrome.app.runtime.onLaunched.addListener(function() {

  chrome.app.window.create('window.html', {

    'outerBounds': {

      'width': 390,

      'height': 438

    }

  });

});

This file lets Chrome know that it should use a file called window.html to create the app's main window (with a width of 390 pixels and a height of 438 pixels).

Because USB access via the powerful chrome.hid.* API is subject to security restrictions, it's vital to tell Chrome, explicitly, ahead of time, which devices you'll need to be able to reach. The manifest.json file is where you do that. This file contains the following information:

{
  "name": "Augusta Chrome View",
  "description": "A demo app by Kas Thomas",
  "version": "0.1",
  "manifest_version": 2,
  "app": {
    "background": {
      "scripts": ["background.js"]
    }
  },
  "icons": { "16": "add-icon-16.png", "128": "add-icon-128.png" },
  "permissions": [
	"browser",
	"hid", {
      		"usbDevices": [ { "vendorId": 2765, "productId": 16400 }, /* Spectrum Pro */
				{ "vendorId": 2765, "productId": 13328 }, /* MiniSmart II */
				{ "vendorId": 2765, "productId": 14368 }, /* Augusta */
				{ "vendorId": 2765, "productId": 14624 }, /* Augusta S (SRED) */
				{ "vendorId": 2765, "productId": 13600 }, /* UniPay III */
				{ "vendorId": 2765, "productId": 12592 }, /* UniPay 1.5 */
				{ "vendorId": 2765, "productId": 4176  }  /* L100 */
			      ]
      }
  ]
}

The manifest.json file makes reference not only to the background.js file mentioned earlier, and the icons associated with your app, but also to various USB-HID products, each of which has a Vendor ID as well as a Product ID. (As you can see, various ID TECH products appear in this list. You can add any products you want, though, if you know their VIDs and PIDs.) Note carefully, the VIDs and PIDs in this list must be specified in decimal (base 10) notation, not hex. Your app won't load properly if you use hexadecimal here.

The window.html file contains HTML markup describing the user interface for the app:

<!DOCTYPE html>

<html>

  <head>

        <script src="/script.js"></script>

  </head>

 

  <body style="background-color:#ddd;">

    <div style="font-family:Times,Georgia,serif;color:#474;font-size:24pt;-webkit-user-select: text;">Augusta Chrome Demo</div>

    <div style="font-size:8pt;">Copyright 2016 ID TECH</div><br/>

    <!-- TEXT AREAS -->

    <textarea rows="20" cols="48" id="outputNode"></textarea>    <br />

    <textarea rows="2" cols="48" id="cmd"></textarea>   <br />

 

    <!-- ACTION BUTTONS (SEND AND CLEAR) -->

    <input type="button" value="Send" id="actionButton" />

    <input type="button" value="Clear" id="clearButton" />  

 

    <!-- COMMAND EXAMPLES to pre-load -->

    <select id="selection">

  <option value=""><b>Select a command and hit Send...</b></option>

  <option value="78 46 01">Get Firmware Version</option>

  <option value="78 46 20">Get Model Number</option>

  <option value="78 46 02">Get Serial Number</option>

  <option value="78 52 01 50">Get Date and Time</option>

  <option value="72 46 03 01">Retrieve AID List</option>

  <option value="0206007353011a0132081403">MSR Step 1: Enable MSR Buffer Mode</option>

  <option value="0205007346500130543a03">MSR Step 2: Arm MSR to Read</option>

  <option value="0205007346510130553b03">MSR Step 3: Read the MSR Buffer</option>

  <option value="021e007246050101001e001e9f02060000000001009f03060000000000009c0100ace803">Start EMV Transaction</option>

    </select>

  </body>

</html>

The UI is quite simple. It consists of a couple of text areas, two buttons (Send and Clear), and a dropdown menu (otherwise known as a combo box) pre-populated with some commands we can run against the Augusta.

"But where's the code?" you're probably asking. There is no code in the HTML itself: there's just a reference to the script.js file, where all the code actually lives. Keeping the code separate from the HTML allows us to maintain clean separation between the app's presentation and its actual logic. This allows us (among other things) to make changes in the app's look and feel, later on, without any chance of introducing bugs in the code.

The code is contained in a single file, script.js. It's about 250 lines long and is broken up into 21 short functions.

/*	AUGUSTA CHROME DEMO by Kas Thomas 
	Copyright 2016 ID TECH. All rights reserved. 
	You may use and copy this code at your own risk.
*/

var MY_HID_VENDOR_ID  = 0x0acd; // VID
var MY_HID_PRODUCT_ID = 0x3820; // Augusta
var DEVICE_INFO = 	{ "vendorId": MY_HID_VENDOR_ID, "productId": MY_HID_PRODUCT_ID };
var connectionId = 	null;  // global
var outputNode = 	null;  // global
var POLL_INTERVAL =	16;  // poll device every 16 msec

// Add event listeners to UI
document.addEventListener('DOMContentLoaded', function () {
    document.getElementById('actionButton').addEventListener( 'click', myAction );
    document.getElementById('clearButton').addEventListener( 'click', clearAll );
    document.getElementById('selection').addEventListener( 'change', writeToUserText );  
});

// SEND BUTTON
function myAction() {

    var cmd = document.getElementById("cmd");
    var userText = cmd.value;

    try {
	// check if user text looks like 02xxxxxx
    	if ( userText.indexOf("02") == 0  ) {

		writeOutput("\r\n" + "Sent: " + userText);	
		var buffer = commandStringToArrayBuffer( userText );
		chrome.hid.send( connectionId, 0, buffer, function(){  } );
	} 
    	else {
		var cmd = formatCommand( userText ); 
		writeOutput("\r\nSent: " + cmd);
		var buffer = commandStringToArrayBuffer( cmd );
		chrome.hid.send( connectionId, 0, buffer, function(){} );
	}
    }
    catch(e) {
	writeOutput( "myAction() failed: " + e.toString() );
    }	
}

function clearAll() {

    document.getElementById("cmd").value = "";
    document.getElementById("outputNode").value = "";
}

function getSelection() {

    var selection = document.getElementById("selection").value;
    return selection;
}

function writeToUserText( str ) {

	document.getElementById("cmd").value = getSelection();
}

function getOutputNode() {

	if (document)
		outputNode = document.getElementById("outputNode");
        else
		console.log("document was null.");
}

// write to the app's main text area
function writeOutput( value ) {

	if (!outputNode)
		getOutputNode();

	if (outputNode) {
		outputNode.value += value ;
		outputNode.scrollTop = outputNode.scrollHeight; // scroll to bottom
  	}
	else
		console.log("outputNode was null.");
}

// return LRC as hex string;
// input should look like ["1A","22","3C", etc.]
function LRC(ar) {
   var lrc=0;
   for (var i=0;i<ar.length;i++)
     lrc ^= Number("0x" + ar[i]); 
   var hex = lrc.toString(16).toUpperCase();
   return (hex.length < 2) ? "0" + hex : hex;
} 

// return 8-bit checksum as hex string
function checksum(ar) {
   var sum=0;
   for (var i=0;i<ar.length;i++)
     sum += Number("0x" + ar[i]); 
   var hex = (255 & sum).toString(16).toUpperCase();
   return (hex.length < 2) ? "0" + hex : hex;
}

function decimalToHexString( n ) {
   var hex = n.toString(16).toUpperCase();
   return ( hex.length < 2) ? "0" + hex : hex;
}

// Assumes cmd looks like "78 46 01" 
// Here, we need to package it as: 
//    STX + length bytes + cmd + lrc + sum + ETX
function formatCommand( cmd ) {

	var STX = "02"; var ETX = "03";

	// first, collapse spaces
	var input = cmd.replace(/\s+/g,"");

	// break "784601" into [78,46,01]
	var inputArray = input.match(/../g);

	var lrc = LRC( inputArray );
	var sum = checksum( inputArray );
	
	var len = decimalToHexString( inputArray.length ) + "00";

	return STX + len + inputArray.join("") + lrc + sum + ETX;
}

// convert ArrayBuffer to String
function arrayBufferToString( array ) {
    return String.fromCharCode.apply(null, new Uint8Array( array ) );
}

// convert ArrayBuffer to hex string
function arrayBufferToHex( array ) {

	var output = "";
	var dv = new DataView( array );
	var len = dv.byteLength;
	for ( var i = 0; i < len; i++) {

		var item = dv.getUint8( i );
		var hex = item.toString( 16 );
		if (hex.length < 2) 
			hex = '0' + hex;
		output += hex + " ";
	}
	return output;
}

// convert USB report to hex string
function usbReportToHexString( usb ) {

	var usbArray = usb.split(" ");
	var length = usbArray.shift();
	length = 1 * ("0x" + length);
	usbArray = usbArray.slice( 0, length );
	return usbArray.join("");
}

// Take s = "020400724609013cc203" and turn it
// into [2, 4, 0, 114, 70, 9, 1, 60, 194, 3]
function hexStringToNumericArray( s ) {

	var array = s.match(/../g);
	var output = [];

	for ( var i = 0; i < array.length; i++ )
		output.push( 1 * ("0x"+array[i] ) );

	return output;
}

// Convert "020400724609013cc203" (hex string) into an ArrayBuffer prepended with length
function commandStringToArrayBuffer( s ) {

	var numericArray = hexStringToNumericArray(s);

	// make the first byte the length:
	numericArray.unshift( numericArray.length );

	var uint8 = new Uint8Array( numericArray);
	return uint8.buffer;
}

// this will be 2nd arg to chrome.hid.receive
var myReceiver = function( reportID, data ) {

	DATA = data; // DEBUG

	value = "";

        if (data != null) {

            // Convert Byte into Ascii to follow the format of our device
            value = arrayBufferToString(data);

	    // Write to HTML window
	    var output = "\r\n";
	    output += "Received: ";
	    output += usbReportToHexString( arrayBufferToHex( data ) );
 	    output += "\r\n";
	    output += value;
	    writeOutput( output + "\r\n" );
            writeOutput( arrayBufferToHex( data ) + "\r\n" );	   
        }

        setTimeout( myDevicePoll, POLL_INTERVAL );
};


// Enable auto-detect. 
// If user unplugs then replugs the device
// it should auto-detect & connect
chrome.hid.onDeviceAdded.addListener( 
   function( deviceInfo ) {	
	MY_HID_VENDOR_ID = DEVICE_INFO.vendorId = deviceInfo.vendorId;
	MY_HID_PRODUCT_ID = DEVICE_INFO.productId = deviceInfo.productId;
	writeOutput( deviceInfo.productName + " detected...\n" );
	connect();
   } 
);

// poll the device -- this is the arg to initializeHid(), below
var myDevicePoll = function() {

    chrome.hid.receive( connectionId, myReceiver );
}

// Must pass a polling function 'pollHid' to this function
function initializeHid( pollHid ) {

    // Try to open the USB HID device
    chrome.hid.getDevices( DEVICE_INFO, function(devices) {

	var errMsg = 'device not found; try hitting the Reload link on chrome://extensions page' +
			'\n-- or simply unplug/replug the device into the computer\'s USB port.\n';

        if (!devices || !devices.length) {
   	    console.log( errMsg );
	    writeOutput( errMsg );
   	    return;
   	}
   	console.log('Found device: ' + devices[0].deviceId );
   	myHidDevice = devices[0].deviceId;

        // Connect to the HID device
   	chrome.hid.connect(myHidDevice, function(connection) {

            console.log('Connected to the HID device!');
	    writeOutput('Connected to the reader!');

   	    connectionId = connection.connectionId;

            // Poll the USB HID Interrupt pipe
            pollHid();
   	});
    });
}

initializeHid( myDevicePoll );  // This executes on code-load

function connect() {
   initializeHid( myDevicePoll );
}

Much of the code is devoted to converting data back and forth from ASCII-representable hex to actual byte data in an ArrayBuffer. The "real work" of communicating with the device gets done in the initializeHid() method, which establishes the USB connection, along with myDevicePoll(), which polls the device, and myReceiver(), which is the callback we provide to chrome.hid.receive(). Each function is short and self-explanatory.

When we send a command (such as the hex bytes 78 52 01 50, for Get Date and Time) to Augusta, we need to package the command in an ID TECH NGA-protocol envelope. This is actually pretty simple. Command strings need to start with STX (hex 0x02) and end with ETX (0x03); the two bytes after STX should be payload length bytes (LSB, then MSB); after that should come the payload itself (i.e., the command plus, optionally, any data bytes); then an LRC value (the 8-bit XOR of all payload bytes), and a checksum (8-bit sum of all payload bytes). When we package the Get Date and Time command in this way, we get a full command string of:

020400785201507B1B03

Notice that the payload is simply 78 52 01 50, hence the length (little-endian) is 04 00. Thus, STX + Length + Command is 02040078520150. The LRC (XOR) of 78 52 01 50 is 7B, and the checksum is 1B. The final byte is ETX: 0x03. To send the full command to Augusta over USB, we need to package it as a USB report. For Augusta, this means prepending the entire message with a length byte and putting all the bytes into an ArrayBuffer. We can then send the data to Augusta using chrome.hid.send(), which is defined as:

chrome.hid.send(integer connectionId, integer reportId, ArrayBuffer data, function callback)

In this case, connectionId is the connection identifier we got back from the connection attempt; reportId is zero; data is the buffer containing our byte data; and callback is empty (not used in this case). 

Receiving data is almost as easy: We just need to call chrome.hid.receive(), passing it a connectionId value and a callback function. The callback looks like this:

var myReceiver = function( reportID, data ) {

	value = "";

        if (data != null) {

            // Convert Byte into Ascii to follow the format of our device
            value = arrayBufferToString(data);

	    // Write to HTML window
	    var output = "\r\n";
	    output += "Received: ";
	    output += usbReportToHexString( arrayBufferToHex( data ) );
 	    output += "\r\n";
	    output += value;
	    writeOutput( output + "\r\n" );
                  writeOutput( arrayBufferToHex( data ) + "\r\n" );	   
        }

        setTimeout( myDevicePoll, POLL_INTERVAL );
};

Chrome calls this function at the proper time and passes it a report number (generally zero, for Augusta) and a data buffer. It's our job to convert the buffer to displayable ASCII and then set up the next chrome.hid.receive() call to occur after an interval of 16 milliseconds (defined in POLL_INTERVAL). The data buffer we get from the device will be 64 bytes long (because that's the defined report size for Augusta). We have to get the true length of the data from the first byte of the buffer. It's all much simpler than it sounds, and the code is quite straightforward.

 

Getting Chrome USB Code to Work on Linux

The code shown here will work on Linux (if Chrome is present), but only if your USB permissions are set properly. (By default, they probably won't be!) You'll almost certainly need to modify your USB-HID device permissions. Otherwise, Chrome will not be able to claim the USB device away from the OS.

On Linux, Chrome uses the device nodes at /dev/hidraw* to access your USB HID device. Check the permissions using the following command in a terminal session. If you see your device belongs to the group 'root':

$ ls -l /dev/hidraw* 
crw------- 1 root root 251, 0 Jul 11 18:46 /dev/hidraw0 
crw------- 1 root root 251, 1 Jul 11 19:23 /dev/hidraw1

... then you will need to tell Linux to attach the /dev/hidraw* devices to the group 'plugdev,' as explained below. (You should belong to this group already. You can check it by running the command 'id'.) To set your permissions, create a file called:

/etc/udev/rules.d/99-hidraw-permissions.rules 

and add this line:

KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev"

(Use gedit or your favorite Linux editor for this.) Finally, restart the udev service:

sudo service udev restart

Then unplug/plug your USB-HID device. Now you should get the following when you run ls -l /dev/hidraw*

$ ls -l /dev/hidraw* 
crw------- 1 root root 251, 0 Jul 11 18:46 /dev/hidraw0
crw-rw-r-- 1 root plugdev 251, 1 Jul 11 20:26 /dev/hidraw1

 

Conclusion

The techniques shown here will let you talk to a variety of USB devices from Chrome using JavaScript. Note that some devices use USB Report 1, Report 2, etc., in such a way that data spanning multiple reports is chained using a technique wherein Report 2 is the start of a multiple-report message, reports with a Report Number of 3 will be intermediate reports in which every byte is used (each report is "full"), and Report 4 is the "trailer" (terminal report) containing the final bytes of the message. This technique is not hard to implement, but is not shown here.

What we've seen here is:

1. How to open, read, and write a USB connection using the chrome.hid.* API.

2. Send data to and from ID TECH's Augusta card reader.

3. Package data according to the ID TECH NGA protocol (which starts with STX and ends with ETX).

4. Load a Chrome app using the chrome://extensions mechanism.

5. Get the code to work in Linux.

I hope you'll agree with me, these are all good things to know how to do. Of course, we haven't actually talked about how to do a card swipe and analyze the resulting data. We'll talk about that (and much more) in future posts.

Tagged in: Augusta Chrome EMV MSR USB
Hits: 3836
0

One of the nice things about ID TECH card readers is how ridiculously easy it is to communicate with them. Did you know, for example, that you can talk to most of our payment peripherals via your Google Chrome web browser? Let's talk about how to do that, because it's tremendously handy to be able to talk to serial and/or USB devices using nothing more than JavaScript and HTML.

For this article, I'm going to focus on the RS-232 version of ID TECH's SRED-compliant SecuRED card reader (pictured below), which is a full-time-encrypting magnetic swipe reader (MSR) that can communicate via USB-HID, USB-KB, or RS-232. In future articles, we'll talk in depth about how to connect to ID TECH readers over USB-HID (in Chrome). For today, we'll concentrate on RS-232, which is slightly easier than USB, while illustrating many of the techniques that we'll be using later for USB-HID communications.SecuRED

If you're saying "But my computer doesn't even have an RS-232 port," relax. You can connect a serial device to your computer using a serial-to-USB adapter cable (by Prolific, or another vendor). The serial data will still look like serial data, even though it's coming through the USB port.

Google's Chrome browser offers RS-232 connectivity via the chrome.serial.* API, but note carefully, the API in question is not available to ordinary web pages. To use the serial API, you have to implement a Chrome app and install that app using the Chrome extensions mechanism (which isn't at all hard to do). Let's take a look at what's involved.

To create a Chrome app, you just need to put half a dozen small files in a folder. The complete, already-built app for today's example (including the 6 small files you'll need) can be found here:

Serial-Test-Tool.zip

Download the Zip file to your local drive and unpack it to its own folder. It should contain the following files:

b2ap3_thumbnail_SERIALFILES.png

I'll talk about how to install the app as a Chrome extension in just a minute. But right now, let's quickly look at the files that make up the app.

The background.js file is a text file containing the following text:

chrome.app.runtime.onLaunched.addListener(function() {
  chrome.app.window.create('window.html', {
    'outerBounds': {
      'width': 390,
      'height': 438
    }
  });
}); 

This file lets Chrome know that it should use a file called window.html to create the app's main window (with a width of 390 pixels and a height of 438 pixels).

The next two files are simply small PNG icons.

The manifest.json file is a text file containing the following:

 {
  "name": "Serial Test Utility",
  "description": "A Chrome App for reading serial connections. Copyright 2016 ID TECH.",
  "version": "0.1",
  "manifest_version": 2,
  "app": {  "background": { "scripts": ["background.js"] } },
  "icons": { 	"16": "IDTechLogo16.png", 
		"128": "IDTechLogo128.png" 
	   },
  "permissions": [ "browser", "serial" ]
}

The manifest is an important file, because Chrome apps run in a unique security environment in which (for example) permissions must be specified explicitly, in advance. The manifest tells Chrome which permissions are needed, which icons should be associated with the app, and other essential info.

The script.js file (which will be referenced from window.html) contains all of the JavaScript that makes our app function. It consists of the following code:

/* 
Serial Test Utility
by Kas Thomas
Copyright 2016 by ID TECH
Visit: idtechproducts.com

Code is provided for educational purposes only. 
Use at your own risk.
*/

var COM3 = "COM3"; // for Linux, change this to something like "/dev/tty0" or "/dev/ttyUSB0"
var connectionId;
var outputNode = document.getElementById("outputNode"); // get the text area node
var WAIT_TIME = 500; // accumulate data for 500 msec
var options = {
  'bitrate': 19200,
  'dataBits': 'eight',
  'parityBit': 'no',
  'stopBits': 'one'
}

// UI
document.addEventListener('DOMContentLoaded', function () {
    document.getElementById('actionButton').addEventListener( 'click', btnSend );
    document.getElementById('clearButton').addEventListener( 'click', clearAll );
    document.getElementById('selection').addEventListener( 'change', writeToUserText );
    document.getElementById('connectButton').addEventListener( 'click', connect );
    enumerate();
    connect(COM3);
});

function enumerate() {
 // On startup:
 chrome.serial.getDevices( function( devices ) {
   for (var i = 0; i < devices.length; i++) {
       	var info = devices[i];
       	writeOutput( "\nDevice " + i + "\n");
       	writeOutput( "   path: " + info.path + "\n");
       	writeOutput( "   vendorId: " + info.vendorId + "\n");
       	writeOutput( "   displayName: " + info.displayName + "\n");
   	}
   } );
}

function connect( port ) { 

try {
	chrome.serial.connect( port, options, function(info) {
                if (!info)
			return;
    		connectionId = info.connectionId;
    		console.log("Connection established.");
		writeOutput("\nConnection established.");

		try {
		for (var k in info)
			writeOutput("\n" + k + ": " + info[k].toString() ); 
		}
		catch(e) { console.log("Error: " + e.toString() ); }
  	});
   } 
   catch(e) { console.log("Exception while trying to connect: " + e.toString() ); }
}

function clearAll() {
    document.getElementById("cmd").value = "";
    document.getElementById("outputNode").value = "";
}

function getSelection() {
    var selection = document.getElementById("selection").value;
    return selection;
}

function writeToUserText( str ) {
    document.getElementById("cmd").value = getSelection();
}

// Take s = "020400724609013cc203" and turn it
// into [2, 4, 0, 114, 70, 9, 1, 60, 194, 3]
function hexStringToNumericArray( s ) {
	var array = s.match(/../g);
	var output = [];

	for ( var i = 0; i < array.length; i++ )
		output.push( 1 * ("0x"+array[i] ) );

	return output;
}

function commandStringToArrayBuffer( s ) {
	var numericArray = hexStringToNumericArray(s);
	var uint8 = new Uint8Array( numericArray);
	return uint8.buffer;
}

// SEND
var btnSend = function() {
  var ab;
  var cmd = document.getElementById("cmd");
  var userText = cmd.value;
  
  cmd = userText.replace(/\s/g,""); // just pass the command as-is
  ab = commandStringToArrayBuffer( cmd );
  chrome.serial.send(connectionId,   ab , function() {} );
  writeOutput( "\nSent: " + cmd.match(/../g).join(" ") );
}

// convert ArrayBuffer to hex string
function arrayBufferToHex( array ) {
	var output = "";
	var dv = new DataView( array );
	var len = dv.byteLength;
	for ( var i = 0; i < len; i++) {
		var item = dv.getUint8( i );
		var hex = item.toString( 16 );
		if (hex.length < 2) 
			hex = '0' + hex;
		output += hex + " ";
	}

	return output;
}

// write to the app's HTML page
function writeOutput( value ) {
	if (document)
		outputNode = document.getElementById("outputNode");
	else throw ("No output node!");

	if (outputNode) {
		outputNode.value += value ;
		outputNode.scrollTop = outputNode.scrollHeight;
	}
	else
		console.log("outputNode was null.");
}

responseData = { busy:false, data:"" }; // accumulate response data here

function report() { 
	writeOutput( "\nReceived:\n" + responseData.data + "\n");
	responseData.busy = false;
	responseData.data = "";
}

function collect( text ) {
	if ( responseData.busy == false )
		setTimeout( report, WAIT_TIME );
	responseData.busy = true;
	responseData.data += text;
}

receiver = function( info ) {	
	var data = info.data;
	var str = arrayBufferToHex( data );
	collect( str ); 
        console.log( "Received: " + str + "\r\nFrom: " + info.connectionId );
};

chrome.serial.onReceive.addListener(receiver);

Okay. That may not seem like a small file, but in reality, we're talking about less than 200 lines of code, which (in the scheme of things) is actually a pretty trivial amount of code.

Finally, there's window.html, which is the file containing our app's HTML markup (the user interface for the app). It consists of the following markup:

<!DOCTYPE html> 
<html>
<head>
<script src="/script.js"></script>
</head>

<body style="background-color:#ddd;">

<div style="font-family:Times,Georgia,serif;color:#242;font-size:24pt;">Serial Test Utility</div>
<div style="font-size:8pt;">Copyright 2016 ID TECH</div><br/>

<!-- CONNECT BUTTON -->
<input type="button" value="Connect" id="connectButton" />

<!-- TEXT AREAS -->
<textarea rows="14" cols="48" id="outputNode">A test utility to send NGA commands over a serial connection.</textarea>

<br />
<textarea rows="2" cols="48" id="cmd"></textarea>
<br />

<!-- ACTION BUTTONS (SEND AND CLEAR) -->
<input type="button" value="Send" id="actionButton" />
<input type="button" value="Clear" id="clearButton" />

<!-- COMMAND EXAMPLES to pre-load -->
<select id="selection">
<option value=""><b>Select a command and hit Send...</b></option>
<option value="02 52 A2 03 F1">SecuRED Get Processor Firmware Version</option>
<option value="02 52 22 03 71">SecuRED Get MSR Firmware Version</option>
<option value="02 53 11 01 33 03 71">SecuRED Beep (Low Freq)</option>
<option value="02 53 49 01 06 03 1C">SecuRED Unmask 6 Leading Chars</option>
<option value="02 52 51 03 02">SecuRED Get KSN</option>
</select>

</body>
</html>

Notice that there is no inline JavaScript anywhere in the page. One-hundred percent of the app's logic is in an external file (the script.js file). This makes for very clean separation of program logic from presentation markup, which in turn greatly facilitates code maintenance and debugging.

Rather than walk through the code (which is pretty self-explanatory), let's talk about how to install and run it. 

Launch your Chrome browser, if you haven't already done so, and navigate to your chrome://extensions page. In the upper right corner, find and check the Developer Mode checkbox. See below.

b2ap3_thumbnail_DeveloperMode.png

When you've checked the Developer Mode checkbox, and only when you've checked it, the Load unpacked extensions... button (on the left) will appear. Click that button. Navigate to the folder containing your 6 small files. Just designate that as the target folder and click OK. You'll be back in the chrome://extensions page, and the app will appear in the list of available Extensions. Find the app and click the Launch link associated with it (look carefully in the above screenshot). The app's main window will pop into view:

b2ap3_thumbnail_AppWindow.png

This is what the window will look like if you've already got a SecuRED card reader (or other serial device!) plugged into your COM3 port. Note that the JavaScript in script.js (see further above) is hard-coded to expect to use "COM3" as the port name. If your computer uses a different serial port, make a manual edit to the code, to use the correct name. (You can find this out from "Devices and Printers" in Windows. In Linux, your serial port may have a name like /dev/tty0 or /dev/ttyUSB0 or /dev/tty1.) And yes,  by the way, this extension will work in Windows, or on Linux, or on Mac OS X, or ChromeOS, assuming Chrome is installed.

If you've connected successfully to your serial device, and that device happens to be ID TECH's SecuRED card reader, go ahead and swipe a credit card through the slot. The card data will appear automatically in the top text area  of the app window (note the hex values in the screenshot below):

 b2ap3_thumbnail_CARD_DATA.png

Of course, knowing how to parse this data requires an understanding of ID TECH's Enhanced Encrypted MSR Format. (Likewise, decrypting the data requires that you understand DUKPT, key derivation, AES, and/or TDES.) We'll discuss that in a future post. For now, it's enough to note that the Serial Test Utility allows you to capture credit-card data instantly, and automatically, from Google Chrome, on any platform where Google Chrome runs. And you can use Chrome's fantastic built-in debugging tools to debug your code, which is just JavaScript.

If you step through the code, you'll see (all the way at the bottom of the code) that in order to receive serial data, we pass a receive callback function to chrome.serial.onReceive.addListener().The receive() function, in turn, calls a collect() function that accumulates received bytes into a buffer. The buffer is written (as text) to the app UI after a WAIT_TIME of 500 milliseconds. That is, when data comes in to the app from the card reader, we start a timer, and we accumulate data (as it arrives in small batches, a few milliseconds apart) into a buffer, then when the timer times out (after 500 msec) we flush the buffer to the screen (and start the buffering process over, if data is still coming). This allows us to batch a reasonable amount of data before putting it on-screen. (Otherwise, we'd potentially be writing lots of very short packets to the screen at intervals of a few milliseconds.) 

The only other parts of the app that might not be self-evident are the small "command window" just above the Send and Clear buttons (see screenshot above), and the dropdown menu ("Select a command and hit Send"). The small text area above the Send button is an area where you can manually enter specific firmware commands that the target device (ID TECH's SecuRED, in this case) knows how to respond to. The dropdown menu contains a few pre-loaded commands (like 02 53 11 01 33 03 71, which makes the SecuRED beep once) all ready to go. If you happen to know other hex commands (because maybe you've got the unit's documentation in front of you?), you can enter commands that control or configure the device. 

Even if you don't use the SecuRED-specific dropdown menu control, the Serial Test Utility is a handy tool for sniffing any serial connection from your Chrome browser. It will display any data that crosses the serial port, whether it originated from an ID TECH card reader or not.

I hope you enjoyed this project. In an upcoming post, we'll look at what it takes to communicate with a USB-HID device using Chrome's connectivity APIs. That's definitely worth knowing how to do!

 

 

Hits: 3294
0

"Customer service and satisfaction are key factors in our position and growth in the industry."

Contact Sales

Stay up-to-date our new products & promotions

Toll Free Number
1-800-984-1010