@@ -65,8 +65,8 @@ defmodule NimbleTOTP do
6565 uri |> EQRCode.encode() |> EQRCode.svg()
6666 #=> "<?xml version=\\" 1.0\\" standalone=\\" yes\\" ?>\\n<svg version=\\" 1.1\\" ...
6767
68- uri = NimbleTOTP.otpauth_uri("Acme", "alice", secret, digits: 8)
69- #=> "otpauth://totp/Acme:alice?secret=MFRGGZA&issuer=Acme"
68+ uri = NimbleTOTP.otpauth_uri("Acme", "alice", secret, digits: 8, algorithm: :sha512 )
69+ #=> "otpauth://totp/Acme:alice?secret=MFRGGZA&issuer=Acme&digits=8&algorithm=sha512 "
7070 uri |> EQRCode.encode() |> EQRCode.svg()
7171 #=> "<?xml version=\\" 1.0\\" standalone=\\" yes\\" ?>\\n<svg version=\\" 1.1\\" ...
7272
@@ -138,6 +138,7 @@ defmodule NimbleTOTP do
138138 import Bitwise
139139 @ default_digits 6
140140 @ default_totp_period 30
141+ @ default_algorithm :sha
141142
142143 @ typedoc "Unix time in seconds, `t:DateTime.t/0` or `t:NaiveDateTime.t/0`."
143144 @ type time ( ) :: DateTime . t ( ) | NaiveDateTime . t ( ) | integer ( )
@@ -224,6 +225,8 @@ defmodule NimbleTOTP do
224225 If this option is given to `verification_code/2`, it must also be given to `valid?/3`.
225226 * `:digits` - The desired length of the totp. Default is 6.
226227 If this option is given to `verification_code/2`, it must also be given to `valid?/3` and `otpauth_uri/3`/`otpauth_uri/4`.
228+ * `:algorithm` - The algorithm to use for the totp. Default is :sha.
229+ If this option is given to `verification_code/2`, it must also be given to `valid?/3` and `otpauth_uri/3`/`otpauth_uri/4`.
227230
228231 ## Examples
229232
@@ -237,31 +240,37 @@ defmodule NimbleTOTP do
237240 time = opts |> Keyword . get_lazy ( :time , fn -> System . os_time ( :second ) end ) |> to_unix ( )
238241 period = Keyword . get ( opts , :period , @ default_totp_period )
239242 digits = Keyword . get ( opts , :digits , @ default_digits )
243+ algorithm = Keyword . get ( opts , :algorithm , @ default_algorithm )
240244
241245 digits not in 6 .. 10 && raise ArgumentError , "digits must be between 6 and 10"
242246
243- verification_code ( secret , time , period , digits )
247+ algorithm not in [ :sha , :sha256 , :sha512 ] &&
248+ raise ArgumentError , "algorithm must be one of :sha, :sha256, :sha512"
249+
250+ verification_code ( secret , time , period , digits , algorithm )
244251 end
245252
246- @ spec verification_code ( binary ( ) , integer ( ) , pos_integer ( ) , integer ( ) ) :: binary ( )
247- defp verification_code ( secret , time , period , digits ) do
253+ @ spec verification_code ( binary ( ) , integer ( ) , pos_integer ( ) , integer ( ) , atom ( ) ) :: binary ( )
254+ defp verification_code ( secret , time , period , digits , algorithm ) do
248255 secret
249- |> hmac ( time , period )
256+ |> hmac ( time , period , algorithm )
250257 |> hmac_truncate ( )
251258 |> rem ( Integer . pow ( 10 , digits ) )
252259 |> to_string ( )
253260 |> String . pad_leading ( digits , "0" )
254261 end
255262
256- defp hmac ( secret , time , period ) do
263+ defp hmac ( secret , time , period , algorithm ) do
257264 moving_factor = << Integer . floor_div ( time , period ) :: 64 >>
258- hmac_sha ( secret , moving_factor )
265+ hmac_sha ( secret , moving_factor , algorithm )
259266 end
260267
261- defp hmac_sha ( key , data ) , do: :crypto . mac ( :hmac , :sha , key , data )
268+ defp hmac_sha ( key , data , algorithm ) , do: :crypto . mac ( :hmac , algorithm , key , data )
262269
263270 defp hmac_truncate ( hmac ) do
264- << _ :: 19 - binary , _ :: 4 , offset :: 4 >> = hmac
271+ key_length = byte_size ( hmac ) - 1
272+
273+ << _ :: size ( key_length ) - binary , _ :: 4 , offset :: 4 >> = hmac
265274 << _ :: size ( offset ) - binary , p :: 4 - binary , _ :: binary >> = hmac
266275 << _ :: 1 , bits :: 31 >> = p
267276 bits
@@ -284,6 +293,9 @@ defmodule NimbleTOTP do
284293 * `:digits` - The desired length of the totp. Default is 6.
285294 If this option is given to `verification_code/2`, it must also be given to `valid?/3` and `otpauth_uri/3`/`otpauth_uri/4`.
286295
296+ * `:algorithm` - The algorithm to use for the totp. Default is :sha.
297+ If this option is given to `verification_code/2`, it must also be given to `valid?/3` and `otpauth_uri/3`/`otpauth_uri/4`.
298+
287299 ## Preventing TOTP code reuse
288300
289301 The `:since` option can be used to prevent TOTP codes from being reused. When set
@@ -317,10 +329,14 @@ defmodule NimbleTOTP do
317329 time = opts |> Keyword . get ( :time , System . os_time ( :second ) ) |> to_unix ( )
318330 period = Keyword . get ( opts , :period , @ default_totp_period )
319331 digits = Keyword . get ( opts , :digits , @ default_digits )
332+ algorithm = Keyword . get ( opts , :algorithm , @ default_algorithm )
320333
321334 digits not in 6 .. 10 && raise ArgumentError , "digits must be between 6 and 10"
322335
323- code = verification_code ( secret , time , period , digits )
336+ algorithm not in [ :sha , :sha256 , :sha512 ] &&
337+ raise ArgumentError , "algorithm must be one of :sha, :sha256, :sha512"
338+
339+ code = verification_code ( secret , time , period , digits , algorithm )
324340
325341 byte_size ( code ) == byte_size ( otp ) and validate_digits ( code , otp ) == 0 and
326342 not reused? ( time , period , opts )
0 commit comments