### 基础知识

OTP（One Time Passowrd）可以分为两种，HOTP和TOTP，即HMAC-based One Time Password and Time-based OTP。 TOPT并不是一种完全不同的模式，而是HOTP的增强版。

HOTP算法在 RFC4226中进行了描述，大约有35页，包含了格式描述、例子等。

HMAC stands for Hash-based Message Authentication Code.

MAC is a way of proving that a message came from the expected sender and not someone else. MAC algorithm produces a MAC tag using a secret key that is only known to the sender and the receiver. 消息认证码的输入包括任意长度的消息和一个发送者与接收者之间之间共享的密钥，它可以输出固定长度的数据，这个数据称为MAC值。

MAC itself is not a specific algorithm, but rather a term that refers to one.

HMAC, in turn, IS a specific implementation. Or, to be more precise — HMAC-X, where X is one of the crypthographic hash functions. Now, HMAC takes two parameters — a secret key and your message, mixes them together in a special way, applies a hash function of your choice twice and produces a MAC tag.

RFC4226定义的HOTP如下：

So, K is predictably used as our secret key and Counter is used as the message.

### 流程与计算

• Generate HMAC-SHA1 value from our K and C parameters. This will be a 20-byte string
• Extract 4 bytes from that string in a specific way
• Convert those bytes into a number, divide that number by 10^n, where n = number of digits in the OTP and take the remainder. Usually n=6.

Crypto API位于window.crypto.subtle

• format will be 'raw', meaning that we will supply the key as raw bytes in an ArrayBuffer.
• keyData is the ArrayBuffer mentioned above. We'll talk about generating it in a bit
• algorithm will be HMAC-SHA1 as per OTP spec. This has to be an HmacImportParams object
• extractable can be false, since we don't plan to export the key
• And finally, of all possible usages we will only need 'sign'

Our secret key will be a long random string. In reality it could be a sequence of bytes that are not necessarily printable。

With that in place — we are ready to sign! To do that we will just need to use sign function of SubtleCrypto.

Generate a 4-byte string (Dynamic Truncation)
Let Sbits = DT(HS) // DT, defined below,
// returns a 31-bit string

Now we only need to convert what we got from DT to an integer and off we go to stage 3.

Stage 3 is really small. All we need to do now is to divide our resulting number by 10 ** (number of digits in OTP) and take the remainder of that division. This way we basically cut last N digits from the resulting number. The spec mentions that you must extract at least 6 digits and possibly 7 or 8. Theoretically since it's a 31-bit integer you can extract up to 9 digits, but in reality I've never seen anything over 6. Have you?

TOTP

Time-based means that instead of a static counter, current time is used as a moving factor. Or, to be precise, current time step. To calculate this time step we take current unix epoch time (number of milliseconds since 00:00:00 UTC on 1 January 1970) and divide it by a time window (usually 30 seconds). Server usually allows for a bit of time drift to account for imperfections in time sync — about 1 step forwards and backwards depending on the configuration.

Due to time-based scheme being an extension over original algorithm, no changes to the original implementation are required. We will use requestAnimationFrame and check on every tick if we are still inside the time window. If we are not — we will calculate a new time step (counter) and regenerate HOTP with it. Omitting all the administrative code it will look roughly like this: