Skip to content

Instantly share code, notes, and snippets.

@bcc bcc/
Created Mar 14, 2016

What would you like to do?
Extract OATH token seeds from a Gemalto provided PSKC v1 XML file.
# Extract OATH token seeds from a Gemalto provided PSKC v1 XML file.
# This script will *only* work if the data is aes128-cbc encrypted, and the
# passphrase is strengthened using PBKDF2.
# I've tested this with a file provided for the IDProve 100 / Easy OTP Token v3.
#, 2013
use strict;
use warnings;
# RFC mode gives us google authenticator compatible output.
use MIME::Base32 qw( RFC );
use MIME::Base64;
use XML::Simple;
use Crypt::CBC;
use Data::Dumper;
use Crypt::PBKDF2;
use Digest::HMAC_SHA1;
# Correct for AES128 only.
my $aes_key_length = 16;
sub aes_decrypt($$$) {
# Raw binary here
my $key = shift;
my $data = shift;
my $keysize = shift;
my $iv = substr($data, 0, $keysize);
my $enc = substr($data, $keysize);
my $cipher = Crypt::CBC->new(
-key => $key,
-keysize => $keysize,
-iv => $iv,
-cipher => "Crypt::OpenSSL::AES",
-literal_key => 1,
-header => 'none',
my $decrypted = $cipher->decrypt($enc);
return $decrypted;
# Does the message MAC match? This provides verification that we have
# valid data and conveniently the correct passphrase as the MAC is encrypted.
sub check_digest($$$) {
my $mac_key = shift;
my $data = shift;
my $digest = shift;
$digest =~ s/=+$//;
my $hmac = Digest::HMAC_SHA1->new($mac_key);
my $t_digest = $hmac->b64digest;
return ($t_digest eq $digest);
# Load the PSKC XML document.
unless ($ARGV[0] && -f $ARGV[0]) {
print "Usage $0 <pskc.xml>\n";
my $ref = XMLin($ARGV[0], ForceArray => 0);
# Check for PBKDF2 key strengthening, as this script won't work with anything else without work.
my $key_algo = $ref->{'pskc:EncryptionKey'}->{'xenc11:DerivedKey'}->{'xenc11:KeyDerivationMethod'}->{'Algorithm'};
if ($key_algo ne '') {
print "pbkdf2 required and not found, exiting\n";
exit 1;
# Extract PBKDF2 parameters.
my $ksdetails = $ref->{'pskc:EncryptionKey'}->{'xenc11:DerivedKey'}->{'xenc11:KeyDerivationMethod'}->{'pkcs5:PBKDF2-params'};
my $key_length = $ksdetails->{'KeyLength'};
my $key_salt = $ksdetails->{'Salt'}->{'Specified'};
my $key_iter_count = $ksdetails->{'IterationCount'};
# Get passphrase. This will be printed to terminal as you type, but will accept a line from STDIN:
# cat key.txt | perl pskcv1.xml
print "Enter Passphrase\n";
my $origkey = <STDIN>;
# Derive new key from the original
my $salt = decode_base64($key_salt);
my $pbkdf2 = Crypt::PBKDF2->new(
hash_class => 'HMACSHA1',
iterations => $key_iter_count,
output_len => $key_length,
salt_len => length($salt),
my $key = $pbkdf2->PBKDF2($salt, $origkey);
# Extract the MAC key for verifying data.
my $crypted_mac = $ref->{'pskc:MACMethod'}->{'pskc:MACKey'}->{'xenc:CipherData'}->{'xenc:CipherValue'};
my $mac_key = aes_decrypt($key, decode_base64($crypted_mac), $aes_key_length);
# Verify MAC then decrypt data for every token.
my $tokens = $ref->{'pskc:KeyPackage'};
foreach my $token (@$tokens) {
my $token_id = $token->{'pskc:Key'}->{'Id'};
my $token_valuemac = $token->{'pskc:Key'}->{'pskc:Data'}->{'pskc:Secret'}->{'pskc:ValueMAC'};
my $token_ciphervalue = decode_base64($token->{'pskc:Key'}->{'pskc:Data'}->{'pskc:Secret'}->
if (!check_digest($mac_key, $token_ciphervalue, $token_valuemac)) {
print "Token $token_id MAC doesn't match, incorrect passphrase?\n";
exit 1;
my $decrypted = aes_decrypt($key, $token_ciphervalue, $aes_key_length);
# Base32 provides a Google authenticator compatible seed. We also want
# a base16 string for pam_oath, along with device ID.
print $token_id . ", " . MIME::Base32::encode($decrypted) . ", " . unpack('H*', $decrypted) . "\n";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.