Index
OneTimePasswords.OneTimePasswords
OneTimePasswords.AbstractOTP
OneTimePasswords.HOTP
OneTimePasswords.OCRA
OneTimePasswords.TOTP
OneTimePasswords._build_ocra_message
OneTimePasswords._dynamic_truncate
OneTimePasswords._hmac
OneTimePasswords.base32decode
OneTimePasswords.base32encode
OneTimePasswords.exportsvg
OneTimePasswords.generate
OneTimePasswords.generate
OneTimePasswords.generate
OneTimePasswords.generate_secret
OneTimePasswords.qrcode
OneTimePasswords.uri
OneTimePasswords.uri
OneTimePasswords.uri
OneTimePasswords.verify
OneTimePasswords.verify
OneTimePasswords.verify
API
OneTimePasswords.OneTimePasswords
— Modulemodule 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
.
OneTimePasswords.AbstractOTP
— TypeAbstractOTP
Abstract supertype for one-time-password generators.
OneTimePasswords.HOTP
— TypeHOTP()
Counter-based OTP (RFC 4226).
OneTimePasswords.OCRA
— TypeOCRA()
The OATH Challenge-Response Algorithm (RFC 6287).
OneTimePasswords.TOTP
— TypeTOTP()
Time-based OTP (RFC 6238).
OneTimePasswords._build_ocra_message
— Function_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”).
OneTimePasswords._dynamic_truncate
— Method_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.
OneTimePasswords._hmac
— Method_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.
OneTimePasswords.base32decode
— Methodbase32decode(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
.
OneTimePasswords.base32encode
— Methodbase32encode(bytes::Vector{UInt8})::String
Encode a byte vector to a Base32 string according to RFC 4648. Result is always ASCII, using uppercase A-Z and digits 2-7, with =
padding.
See also base32decode
.
OneTimePasswords.exportsvg
— Methodexportsvg(
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
.
OneTimePasswords.generate
— Methodgenerate(::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)
.
OneTimePasswords.generate
— Methodgenerate(::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)
.
OneTimePasswords.generate
— Methodgenerate(::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)
.
OneTimePasswords.generate_secret
— Functiongenerate_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
.
OneTimePasswords.qrcode
— Methodqrcode(
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 topath
) 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
OneTimePasswords.uri
— Methoduri(::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
.
OneTimePasswords.uri
— Methoduri(::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
.
OneTimePasswords.uri
— Methoduri(::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
.
OneTimePasswords.verify
— Methodverify(::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)
.
OneTimePasswords.verify
— Methodverify(::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
OneTimePasswords.verify
— Methodverify(::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)
.