An TOTP library for Zig
0

Configure Feed

Select the types of activity you want to include in your feed.

Zig 99.8%
Makefile 0.2%
8 1 0

Clone this repository

https://tangled.org/karlseguin.tngl.sh/otp.zig https://tangled.org/did:plc:fjd3qrhxqzrbrifbpvgvd3c4
git@tangled.org:karlseguin.tngl.sh/otp.zig git@tangled.org:did:plc:fjd3qrhxqzrbrifbpvgvd3c4

For self-hosted knots, clone URLs may differ based on your setup.



readme.md

OTP for Zig#

Currently only supports TOTP with SHA1 or SHA256.

Zig Version#

This is for Zig 0.16.0. Use the zig-0.15 branch for Zig 0.15 or the dev which may or may not be up to date with zig dev.

Example#

const std = @import("std");
const otp = @import("otp");

pub fn main(init: std.process.Init) !void {
    // You would store secret in the DB with the user
    var secret: [20]u8 = undefined;
    otp.generateSecret(init.io, &secret);

    // base32 encode the secret
    // (for a 20-byte secret, you need a 32 byte buffer)
    var code_buf: [32]u8 = undefined;
    std.debug.print("{s}\n", .{otp.bufEncodeSecret(&code_buf, &secret)});

    // verify a user-supplied totp
    const user_topt = "123456";
    if (otp.totp.verify(user_topt, &secret, .{})) {
        std.debug.print("GOOD!\n", .{});
    }
}

Install#

  1. Add otp.zig as a dependency in your build.zig.zon:
zig fetch --save git+https://github.com/karlseguin/otp.zig#master
  1. In your build.zig, add the otp module as a dependency you your program:
const otp = b.dependency("otp", .{
    .target = target,
    .optimize = optimize,
});

// the executable from your call to b.addExecutable(...)
exe.root_module.addImport("otp", otp.module("otp"));

Secrets#

The first thing to do is to generate a secret:

var secret: [20]u8 = undefined;
otp.generateSecret(io, &secret);

You can pick any size, but 20 bytes, as above, is recommended. You should store the secret along with a user.

generateSecret fills the supplied slice with raw bytes. You can base32 encode the secret using:

// write the base32 encoded secret to the *std.Io.Writer
try otp.encodeSecret(secret, writer);

// OR

// write the base32 encoded secret into "encoded_buf"
var encoded_buf: [32]u8 = undefined;
const encoded = otp.bufEncodeSecret(&encoded_buf, secret);

When using bufEncodedSecret, the supplied buffer must be large enough to hold the encoded value. You can use const len = otp.encodeSecretLen(secret.len) to get the required length.

TOTP#

Config#

Passed to various totp functions:

  • interval: u32 - How long a code should be valid for in seconds. Defaults to 30
  • digits: u8 - Number of digits. Defaults to 6.
  • algorithm: otp.totp.Algorithm - The hash algorithm to use. Defaults to .sha1 (other supported value is .sha256)

otp.totp.generate(buf: []u8, secret: []const u8, config: Config) ![]u8#

Generate a new TOTP code for the current time. buf must be at least config.digits. A slice of buf[0..config.digits] is returned.

otp.totp.generateAt(buf: []u8, secret: []const u8, timestamp: i64, config: Config) ![]u8#

Generate a new TOTP code for the specified time. buf must be at least config.digits. A slice of buf[0..config.digits] is returned.

otp.totp.verify(code: []const u8, secret: []const u8, config: Config) bool#

Verifies that the given code is valid for the current time.

otp.totp.verifyAt(code: []const u8, secret: []const u8, timestamp: i64, config: Config) bool#

Verifies that the given code is valid for the given time.

Example#

var code_buf: [6]u8 = undefined;
const code = otp.totp.generate(&code, &secret, .{});
std.debug.assert(otp.totp.verify(code, &secret, .{}) == true);

Which is a shorthand for:

var code_buf: [6]u8 = undefined;
const now = std.time.timestamp()
const config = otp.TOTP.Config{
    .digits = 6,
    .interval = 30,
    .algorithm = .sha1,
};
const code = try otp.totp.generateAt(&code, &secret, now, config);
std.debug.assert(otp.totp.verifyAt(code, &secret, now config) == true);

When generating a code, the buffer, code_buf above, must be at least config.digits long.

URL#

You can generate a URL (which is what would be put in a QR code) using either the url or bufUrl functions. These functions take their own type of config object:

  • account: []const u8 - The account name. Required
  • issuer: ?[]const u8 - Optional issuer name. Defaults to null
  • config: Config - The TOTP configuration. Default to .{}
bufUrl(buf: []u8, secret: []const u8, uc: URLConfig) ![]u8#

Writes the URL into buf. Returns an error if buf isn't large enough. Returns the URL on success.

url(secret: []const u8, uc: URLConfig, writer: *std.Io.Writer) !void#

Writes the URL using the writer.

// min length of buf will largely depend on the account name, issuer name and length of secret.
var buf: [200]u8 = undefined;
const url = try bufUrl(&buf, secret, .{.account = "Leto", .issuer = "spice.gov", .config = .{.digits = 8} });