Created
March 14, 2016 14:45
-
-
Save bcc/3270a75f618f64cf959e to your computer and use it in GitHub Desktop.
Extract OATH token seeds from a Gemalto provided PSKC v1 XML file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
# 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. | |
# ben@spod.cx, 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); | |
$hmac->add($data); | |
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"; | |
exit; | |
} | |
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 'http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2') { | |
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 pskc.pl pskcv1.xml | |
print "Enter Passphrase\n"; | |
my $origkey = <STDIN>; | |
chomp($origkey); | |
# 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'}-> | |
{'pskc:EncryptedValue'}->{'xenc:CipherData'}->{'xenc:CipherValue'}); | |
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