python3-yubico/util/yubikey-totp
2025-08-13 10:23:20 +02:00

133 lines
3.8 KiB
Python
Executable File

#!/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())