%% Copyright 2011 Steve Davis % % Licensed under the Apache License, Version 2.0 (the "License"); % you may not use this file except in compliance with the License. % You may obtain a copy of the License at % % http://www.apache.org/licenses/LICENSE-2.0 % % Unless required by applicable law or agreed to in writing, software % distributed under the License is distributed on an "AS IS" BASIS, % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % See the License for the specific language governing permissions and % limitations under the License. -module(qrcode_erl_demo). %% Shows how to achieve HOTP/SHA1 with a mobile phone using Google Authenticator. %% %% This module is a rag-bag of supporting functions, many of which are simplified %% extracts from the core libs (?_common, ?_crypto, ?_math, ?_image). This is to %% allow a full-cycle demo without requiring open-sourcing of the entire platform. %% %% @ref QR Code: ISO/IEC 18004 (2000, 1st Edition) %% Google Authenticator Phone App %% iPhone: %% Android: %% Google Authenticator URL Specification % @ref % otpauth://TYPE/LABEL?PARAMETERS % TYPE: hotp | totp % LABEL: string() (usually email address) % PARAMETERS: % digits = 6 | 8 (default 6) % counter = integer() (hotp only, default 0?) % period = integer() (in seconds, totp only, default 30) % secret = binary() base32 encoded % algorithm = MD5 | SHA1 | SHA256 | SHA512 (default SHA1) -include("qrcode_erl.hrl"). -compile(export_all). -define(TTY(Term), io:format(user, "[~p] ~p~n", [?MODULE, Term])). -define(PERIOD, 30). run() -> Passcode = crypto:hash(sha, <<"password">>), run(<<"demo@mydomain.com">>, Passcode, ?PERIOD). run(Domain, Passcode, Seconds) -> PasscodeBase32 = base32:encode(Passcode), Period = list_to_binary(integer_to_list(Seconds)), Token = <<"otpauth://totp/", Domain/binary, "?period=", Period/binary, "&secret=", PasscodeBase32/binary>>, ?TTY({token, Token}), QRCode = qrcode_erl:encode(Token), Image = simple_png_encode(QRCode), Filename = "qrcode_erl.png", ok = file:write_file(Filename, Image), ?TTY({image, filename:absname(Filename)}), QRCode. %% Very simple PNG encoder for demo purposes simple_png_encode(#qrcode_erl{dimension = Dim, data = Data}) -> MAGIC = <<137, 80, 78, 71, 13, 10, 26, 10>>, Size = Dim * 8, IHDR = png_chunk(<<"IHDR">>, <>), PixelData = get_pixel_data(Dim, Data), IDAT = png_chunk(<<"IDAT">>, PixelData), IEND = png_chunk(<<"IEND">>, <<>>), <>. png_chunk(Type, Bin) -> Length = byte_size(Bin), CRC = erlang:crc32(<>), <>. get_pixel_data(Dim, Data) -> Pixels = get_pixels(Data, 0, Dim, <<>>), zlib:compress(Pixels). get_pixels(<<>>, Dim, Dim, Acc) -> Acc; get_pixels(Bin, Count, Dim, Acc) -> <> = Bin, Row = get_pixels0(RowBits, <<0>>), % row filter byte FullRow = binary:copy(Row, 8), get_pixels(Bits, Count + 1, Dim, <>). get_pixels0(<<1:1, Bits/bits>>, Acc) -> Black = binary:copy(<<0>>, 24), get_pixels0(Bits, <>); get_pixels0(<<0:1, Bits/bits>>, Acc) -> White = binary:copy(<<255>>, 24), get_pixels0(Bits, <>); get_pixels0(<<>>, Acc) -> Acc. %% totp() -> Key = crypto:hash(sha, <<"password">>), totp(Key, ?PERIOD). totp(Key, Period) -> T = unow() div Period, {hotp(Key, T - 1), hotp(Key, T), hotp(Key, T + 1)}. %% RFC-4226 "HOTP: An HMAC-Based One-Time Password Algorithm" %% @ref hotp(Key, Count) when is_binary(Key), is_integer(Count) -> HS = crypto:hmac(sha, Key, <>), <<_:19/binary, _:4, Offset:4>> = HS, <<_:Offset/binary, _:1, P:31, _/binary>> = HS, HOTP = integer_to_list(P rem 1000000), Pad = lists:duplicate(6 - length(HOTP), $0), list_to_binary([Pad, HOTP]). -define(UNIX_TIME_ZERO, 62167219200). unow() -> calendar:datetime_to_gregorian_seconds(calendar:universal_time()) - ?UNIX_TIME_ZERO.