#!/usr/bin/env python # # Copyright (c) 2011, Yubico AB # See the file COPYING for licence statement. # """ This program lets you use the HMAC-SHA-1 in your YubiKey to produce OATH TOTP (RFC 6238) codes. To verify the output of this program, first program a YubiKey with the RFC 6238 test key "12345678901234567890" (ASCII) : $ ykpersonalize -2 -ochal-resp -ochal-hmac -ohmac-lt64 \ -o serial-api-visible \ -a 3132333435363738393031323334353637383930 and then examine the OATH codes for the test values (Time) in Appendix B of RFC 6238 (SHA1) : Time SHA1 59 -> 94287082 1111111109 -> 07081804 1234567890 -> 89005924 20000000000 -> 65353130 Like this : $ yubikey-totp --step 30 --digits 8 --time 59 94287082 $ """ import sys import time import struct import yubico import argparse import binascii default_slot=2 default_time=int(time.time()) default_step=30 default_digits=6 def parse_args(): """ Parse the command line arguments """ parser = argparse.ArgumentParser(description = "Generate OATH TOTP codes using a YubiKey", add_help = True, formatter_class = argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable verbose operation' ) parser.add_argument('--debug', dest='debug', action='store_true', default=False, help='Enable debug operation' ) parser.add_argument('--time', dest='time', type=int, default=default_time, required=False, help='Time to use as number of seconds since epoch', ) parser.add_argument('--step', dest='step', type=int, default=default_step, required=False, help='Time step in use (in seconds)', ) parser.add_argument('--digits', dest='digits', type=int, default=default_digits, required=False, help='Length of OTP in decimal digits', ) parser.add_argument('--slot', dest='slot', type=int, default=default_slot, required=False, help='YubiKey slot configured for Challenge-Response', ) args = parser.parse_args() return args def make_totp(args): """ Create an OATH TOTP OTP and return it as a string (to disambiguate leading zeros). """ YK = yubico.find_yubikey(debug=args.debug) if args.debug or args.verbose: print("Version : %s " % YK.version()) if args.debug: print("Serial : %i" % YK.serial()) print("") # Do challenge-response secret = struct.pack("> Q", args.time / args.step).ljust(64, chr(0x0)) if args.debug: print("Sending challenge : %s\n" % (binascii.hexlify(secret))) response = YK.challenge_response(secret, slot=args.slot) # format with appropriate number of leading zeros totp_str = '%.*i' % (args.digits, yubico.yubico_util.hotp_truncate(response, length=args.digits)) return totp_str def main(): """ Main program. """ args = parse_args() otp = None try: otp = make_totp(args) except yubico.yubico_exception.YubicoError as e: print("ERROR: %s" % (e.reason)) return 1 if not otp: return 1 print(otp) return 0 if __name__ == '__main__': sys.exit(main())