Skip to content

Instantly share code, notes, and snippets.

@mikezs
Created April 23, 2024 14:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikezs/aa6dd4c2c0fd10f0318b96244f34d020 to your computer and use it in GitHub Desktop.
Save mikezs/aa6dd4c2c0fd10f0318b96244f34d020 to your computer and use it in GitHub Desktop.
totp.swift
import CryptoKit
import Foundation
extension Data {
init?(base32Encoded string: String) {
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
var bytes: [UInt8] = []
var accum = 0
var bits = 0 // # of valid bits in `accum`
for character in string {
guard let stringIndex = alphabet.firstIndex(of: character) else {
print("Invalid character: \(character)")
return nil
}
let index = alphabet.distance(from: alphabet.startIndex, to: stringIndex)
accum = (accum << 5) | index
bits += 5
if bits >= 8 {
bytes.append(UInt8(truncatingIfNeeded: accum >> (bits - 8)))
bits -= 8
}
}
self.init(bytes)
}
}
func totp(secret: Data, period: TimeInterval = 30, digits: Int = 6) -> String {
var counter = UInt64(Date().timeIntervalSince1970 / period).bigEndian
let counterData = withUnsafeBytes(of: &counter) { Array($0) }
let hash = HMAC<Insecure.SHA1>.authenticationCode(for: counterData, using: SymmetricKey(data: secret))
var truncatedHash = hash.withUnsafeBytes { ptr -> UInt32 in
let offset = ptr[hash.byteCount - 1] & 0x0f
let truncatedHashPtr = ptr.baseAddress! + Int(offset)
return truncatedHashPtr.bindMemory(to: UInt32.self, capacity: 1).pointee
}
truncatedHash = UInt32(bigEndian: truncatedHash)
truncatedHash = truncatedHash & 0x7FFF_FFFF
truncatedHash = truncatedHash % UInt32(pow(10, Float(digits)))
return (String(format: "%0*u", digits, truncatedHash))
}
print(totp(secret: Data(base32Encoded: "JBSWY3DPEHPK3PXP")!)) // Test here: https://totp.danhersam.com/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment