Index

API

OneTimePasswords.OneTimePasswordsModule
module OneTimePasswords

A minimal, fast Julia module for generating and verifying counter-based (HOTP, RFC 4226), time-based (TOTP, RFC 6238) and challenge-response (OCRA, RFC 6287) one-time passwords. Also provides provisioning URIs and QR-codes for authenticator apps.

Examples

julia> using OneTimePasswords

julia> secret = generate_secret();

julia> code = generate(HOTP(), secret, 0; digits=6);

julia> verify(HOTP(), secret, 0, code)
true

julia> account = "alice@example.com";

julia> issuer  = "MyApp";

julia> urilink = uri(HOTP(), secret, account, issuer;
               digits=6, counter=0, algorithm=:SHA1);

julia> svg = qrcode(urilink; format=:svg, size=200, border=2);

julia> tmp_svg = tempname() * "hotp.svg";

julia> open(tmp_svg,"w") do io
           write(io, svg)
       end;

julia> tmp_png = tempname() * "hotp.png";

julia> pngfile = qrcode(urilink; format="png", path=tmp_png);

julia> isfile(pngfile)
true

julia> # qrcode(urilink; format=:ascii, border=1) # Print in the REPL
julia> using OneTimePasswords, Dates

julia> secret = generate_secret();

julia> code = generate(TOTP(), secret; period=Second(30), digits=6);

julia> verify(TOTP(), secret, code; allowed_drift=Second(30))
true

julia> account = "alice@example.com";

julia> issuer  = "MyApp";

julia> urilink = uri(TOTP(), secret, account, issuer; digits=6, 
       period=Second(30));

julia> svg = qrcode(urilink; format=:svg, size=200, border=2);

julia> tmp_svg = tempname() * ".svg";

julia> open(tmp_svg, "w") do io
           write(io, svg)
       end;

julia> tmp_png = tempname() * ".png";

julia> pngfile = qrcode(urilink; format="png", path=tmp_png);

julia> isfile(pngfile)
true

julia> # qrcode(urilink; format=:ascii, border=1) # Print in the REPL
julia> using OneTimePasswords, Dates

julia> secret = generate_secret();

julia> suite = "OCRA-1:HOTP-SHA512-8:QA10-T1M";

julia> dt = DateTime(2020,1,1,0,0,30)
2020-01-01T00:00:30

julia> code = generate(OCRA(), secret;
                          suite=suite,
                          challenge="SIG1400000",
                          timestamp=dt,
                          digits=8,
                          algorithm=:SHA512);

julia> verify(OCRA(), secret, code;
               suite=suite,
               challenge="SIG1400000",
               timestamp=dt + Second(60),
               allowed_drift=Second(60),
               digits=8,
               algorithm=:SHA512)
true

julia> account = "alice@example.com";

julia> issuer  = "MyApp";

julia> urilink = uri(OCRA(), secret, "bob", "MyApp";
            suite=suite,
            challenge="SIG1400000",
            timestamp=dt);

julia> svg = qrcode(urilink; format=:svg, size=200, border=2);

julia> tmp_svg = tempname() * ".svg";

julia> open(tmp_svg, "w") do io
           write(io, svg)
       end;

julia> tmp_png = tempname() * ".png";

julia> pngfile = qrcode(urilink; format="png", path=tmp_png);

julia> isfile(pngfile)
true

julia> # qrcode(urilink; format=:ascii, border=1) # Print in the REPL

See also generate_secret, AbstractOTP, HOTP, TOTP, OCRA, generate, verify uri and qrcode.

source
OneTimePasswords._build_ocra_messageFunction
_build_ocra_message(
    suite::AbstractString,
    counter::Union{Nothing,Int}=nothing,
    challenge::AbstractString="",
    passwordhash::AbstractString="",
    session_info::AbstractString="",
    timestamp::Union{Nothing,Int}=nothing)::Vector{UInt8}

Construct the binary “DataInput” value for OCRA (RFC 6287).

The overall layout is:

DataInput = UTF8(suite) ‖ 0x00 ‖ [ C ] ‖ [ Q ] ‖ [ P ] ‖ [ S ] ‖ [ T ]

where

  • Suite: UTF-8 bytes of the suite string, then a 0x00 separator.
  • C: 8-byte big-endian counter (if suite contains “C”).
  • Q: challenge field (numeric/hex/alpha, padded per Qxxx) (if “Q”).
  • P: password-hash left-padded to the HMAC output length (if PSHAx).
  • S: session_info UTF-8 right-padded to Snnn bytes (if Snnn).
  • T: 8-byte big-endian timestamp (if suite contains “T”).
source
OneTimePasswords._dynamic_truncateMethod
_dynamic_truncate(h::Vector{UInt8}, digits::Int)::String

Perform dynamic truncation (as in HOTP) on the HMAC result h and return the OTP as a zero-padded string of length digits.

source
OneTimePasswords._hmacMethod
_hmac(algorithm::Symbol, key::Vector{UInt8}, 
    msg::Vector{UInt8})::Vector{UInt8}

Compute the HMAC of msg using key and the selected algorithm. Supported algorithms are :SHA1, :SHA256, and :SHA512.

source
OneTimePasswords.base32decodeMethod
base32decode(str::AbstractString)::Vector{UInt8}

Decode a Base32 string (per RFC 4648) into a vector of bytes. The input string is not case sensitive and may contain whitespace or padding (=). Throws an exception on invalid characters or impossible encoding.

See also base32encode.

source
OneTimePasswords.exportsvgMethod
exportsvg(
  msg::AbstractString;
  size::Int=240,
  border::Int=4,
  path::Union{Nothing,String}=nothing,
  darkcolor::String="#000",
  lightcolor::String="#fff"
)::String

Generate an SVG <svg>…</svg> QR-code encoding msg. If path is given, also write the SVG to that file.

Examples

julia> using OneTimePasswords

julia> OneTimePasswords.exportsvg("otpauth://totp/bob?...", size=200);

See also qrcode.

source
OneTimePasswords.generateMethod
generate(::HOTP, secret::AbstractString, counter::Integer;
         digits::Int=6, algorithm::Symbol=:SHA1)::String

Compute HOTP for secret and counter (RFC 4226).

Arguments

  • secret: Base32-encoded shared secret.
  • counter: counter value (Integer).
  • digits: code length (default 6).
  • algorithm: :SHA1, :SHA256, or :SHA512.

Examples

julia> using OneTimePasswords

julia> generate(HOTP(), "JBSWY3DPEHPK3PXP", 0)
"282760"

See also verify(::HOTP).

source
OneTimePasswords.generateMethod
generate(::OCRA, secret::AbstractString;
         suite::AbstractString = "OCRA-1:HOTP-SHA1-6:QN08",
         counter::Union{Nothing, Integer}=nothing,
         challenge::AbstractString="",
         password::AbstractString="",
         session_info::AbstractString="",
         timestamp::Union{Nothing,Integer}=nothing,
         digits::Int=6,
         algorithm::Symbol=:SHA1)::String

Compute an OCRA one-time password (OTP) according to RFC 6287.

Arguments:

  • secret: Base32-encoded shared secret.
  • suite: OCRA suite definition string.
  • counter: Optional counter value. If omitted, 8 zero bytes are used.
  • challenge: The challenge/question string (e.g. numeric or hex).
  • password: Optional password (P) field.
  • session_info: Optional session information (S) field.
  • timestamp: Optional timestamp (T) as an integer (e.g. Unix time).
  • digits: The number of digits in the OTP.
  • algorithm: The hash algorithm to use (:SHA1, :SHA256, or :SHA512).

Examples

julia> using OneTimePasswords, Dates

julia> secret = "M7AB5U4DUCNI4GTUMBMB4QB3LL6RIGOF"; # generate_secret()

julia> code = generate(OCRA(), secret; suite="OCRA-1:HOTP-SHA1-6:QN08",
            challenge="12345678")
"262022"

julia> suite = "OCRA-1:HOTP-SHA512-8:QA10-T1M";

julia> dt = DateTime(2020,1,1,0,0,30);

julia> generate(OCRA(), secret;
                 suite=suite,
                 challenge="SIG1400000",
                 timestamp=dt,
                 digits=8,
                 algorithm=:SHA512)
"76056551"

See also verify(::OCRA).

source
OneTimePasswords.generateMethod
generate(::TOTP, secret::AbstractString;
         time=nothing, period::Period=Second(30), digits::Int=6,
         algorithm::Symbol=:SHA1)::String

Compute a TOTP value for secret at the specified time (RFC 6238), with integer step-countging.

Examples

julia> using OneTimePasswords, Dates

julia> secret = "M7AB5U4DUCNI4GTUMBMB4QB3LL6RIGOF"; # generate_secret()

julia> generate(TOTP(), secret; digits=8);

julia> dt = DateTime(2020,1,1,0,0,30);

julia> generate(TOTP(), secret; time=dt, digits=7, period=Second(30), algorithm=:SHA256)
"9150483"

See also verify(::TOTP).

source
OneTimePasswords.generate_secretFunction
generate_secret([length::Int=20])::String

Generate a cryptographically-strong random secret (byte length length) and return it Base32-encoded. Default is 20 bytes (good for SHA1/TOTP).

Examples

julia> using OneTimePasswords

julia> secret = generate_secret();

See also base32encode, base32decode.

source
OneTimePasswords.qrcodeMethod
qrcode(
  uri::AbstractString;
  format::Union{Symbol,String} = :svg,
  size::Int = 240,
  border::Int = 4,
  path::Union{Nothing,String} = nothing,
  darkcolor::String = "#000",
  lightcolor::String = "#fff"
)::Union{String,String}

Generate a QR-code for a provisioning uri. Supports:

  • SVG (:svg, returns SVG text),
  • Bitmap ("png", "jpg", "gif", writes to path) and
  • Terminal ASCII (:ascii, prints a scannable QR code in the REPL).

Examples

julia> using OneTimePasswords, Dates

julia> secret = "M7AB5U4DUCNI4GTUMBMB4QB3LL6RIGOF"; # generate_secret()

julia> code = generate(TOTP(), secret; period=Second(30), digits=6);

julia> verify(TOTP(), secret, code; allowed_drift=Second(30))
true

julia> urilink = uri(TOTP(), secret, "bob@example.com", "MyApp"; digits=6, 
       period=Second(30));

julia> svg = qrcode(urilink; format=:svg, size=200, border=2);

julia> tmp_svg = tempname() * ".svg";

julia> open(tmp_svg, "w") do io
           write(io, svg)
       end;

julia> tmp_png = tempname() * ".png";

julia> pngfile = qrcode(urilink; format="png", path=tmp_png);

julia> isfile(pngfile)
true

julia> # qrcode(urilink; format=:ascii, border=1) # Print in the REPL

See also uri and exportsvg.

source
OneTimePasswords.uriMethod
uri(::HOTP, secret::AbstractString,
    account::AbstractString, issuer::AbstractString;
    digits::Int=6, counter::Integer=0,
    algorithm::Symbol=:SHA1)::String

Return an otpauth://hotp/... provisioning URI for HOTP.

Examples

julia> using OneTimePasswords

julia> secret = "M7AB5U4DUCNI4GTUMBMB4QB3LL6RIGOF"; # generate_secret()

julia> uri(HOTP(), secret, "bob@example.com", "MyApp"; counter=5)
"otpauth://hotp/MyApp%3Abob%40example.com?secret=M7AB5U4DUCNI4GTUMBMB4QB3LL6RIGOF&issuer=MyApp&digits=6&counter=5&algorithm=SHA1"

See also qrcode.

source
OneTimePasswords.uriMethod
uri(::OCRA, secret::AbstractString,
    account::AbstractString, issuer::AbstractString;
    suite::AbstractString="OCRA-1:HOTP-SHA1-6:QN08",
    digits::Int=6,
    algorithm::Symbol=:SHA1,
    counter::Union{Nothing,Int}=nothing,
    challenge::AbstractString="",
    password::AbstractString="",
    session_info::AbstractString="",
    timestamp::Union{Nothing,Int,DateTime}=nothing
   )::String

Return an otpauth://ocra/... provisioning URI for OCRA.

The label will be issuer:account (percent-escaped), and the query string will include secret, issuer, suite, digits, algorithm, and any of the optional fields you pass in.

julia> using OneTimePasswords

julia> secret = "M7AB5U4DUCNI4GTUMBMB4QB3LL6RIGOF"; # generate_secret()

julia> uri(OCRA(), secret, "bob@example.com", "MyApp");

julia> uri(OCRA(), secret, "alice@site.com", "YourOrg";
            suite="OCRA-1:HOTP-SHA256-8:C-QN08-PSHA1",
            digits=8,
            algorithm=:SHA256,
            counter=5,
            challenge="12345678",
            password="7110eda4d09e062aa5e4a390b0a572ac0d2c0220");

See also qrcode.

source
OneTimePasswords.uriMethod
uri(::TOTP, secret::AbstractString,
    account::AbstractString, issuer::AbstractString;
    digits::Int=6, period::Period=Second(30))::String

Return an otpauth://totp/... provisioning URI for TOTP.

Examples

julia> using OneTimePasswords

julia> secret = "M7AB5U4DUCNI4GTUMBMB4QB3LL6RIGOF"; # generate_secret()

julia> uri(TOTP(), secret, "alice@example.com", "MyApp");

julia> uri(TOTP(), secret, "bob@site.com", "YourApp"; digits=8, period=60)
"otpauth://totp/YourApp%3Abob%40site.com?secret=M7AB5U4DUCNI4GTUMBMB4QB3LL6RIGOF&issuer=YourApp&digits=8&period=60"

See also qrcode.

source
OneTimePasswords.verifyMethod
verify(::HOTP, secret::AbstractString, counter::Integer,
       code::AbstractString; digits::Int=6,
       algorithm::Symbol=:SHA1)::Bool

Return true if code matches the HOTP for secret and counter.

Examples

julia> using OneTimePasswords

julia> secret = generate_secret();

julia> code = generate(HOTP(), secret, 123; digits=6);

julia> verify(HOTP(), secret, 123, code)
true

julia> verify(HOTP(), secret, 124, code)
false

See also generate(::HOTP).

source
OneTimePasswords.verifyMethod
verify(::OCRA, secret::AbstractString, code::AbstractString;
       suite::AbstractString = "OCRA-1:HOTP-SHA1-6:QN08",
       counter::Union{Nothing, Integer}=nothing,
       challenge::AbstractString="",
       password::AbstractString="",
       session_info::AbstractString="",
       timestamp::Union{Nothing,Integer}=nothing,
       allowed_drift::Period=Second(0),
       digits::Int=6,
       algorithm::Symbol=:SHA1)::Bool

Verify that the provided code matches the OCRA OTP.

Examples

julia> using OneTimePasswords, Dates

julia> secret = generate_secret();

julia> code = generate(OCRA(), secret; challenge="12345678");

julia> verify(OCRA(), secret, code; challenge="12345678")
true
julia> using OneTimePasswords, Dates

julia> secret = generate_secret();

julia> suite = "OCRA-1:HOTP-SHA512-8:QA10-T1M";

julia> dt = DateTime(2020,1,1,0,0,30)
2020-01-01T00:00:30

julia> code2 = generate(OCRA(), secret;
                          suite=suite,
                          challenge="SIG1400000",
                          timestamp=dt,
                          digits=8,
                          algorithm=:SHA512);

julia> verify(OCRA(), secret, code2;
               suite=suite,
               challenge="SIG1400000",
               timestamp=dt + Second(60),
               allowed_drift=Second(60),
               digits=8,
               algorithm=:SHA512)
true
source
OneTimePasswords.verifyMethod
verify(::TOTP, secret::AbstractString, code::AbstractString;
       period::Period=Second(30), allowed_drift::Period=Second(30),
       digits::Int=6, time=nothing,
       algorithm::Symbol=:SHA1)::Bool

Return true if code is a valid TOTP for secret at time, allowing ±allowed_drift time window.

Examples

julia> using OneTimePasswords, Dates

julia> secret = "M7AB5U4DUCNI4GTUMBMB4QB3LL6RIGOF"; # generate_secret()

julia> dt = DateTime(2022,1,1,0,0,30);

julia> code = generate(TOTP(), secret; time=dt, digits=8);

julia> verify(TOTP(), secret, code; time=dt, digits=8)
true

julia> verify(TOTP(), secret, code; time=dt+Minute(1), digits=8,
                           allowed_drift=Second(60))
true

julia> verify(TOTP(), secret, code; time=dt+Minute(1), digits=8,
                           allowed_drift=Second(30))
false

See also generate(::TOTP).

source