Skip to content

Commit 8bd7fd6

Browse files
committed
Merge branch 'main' into add-repr-c
# Conflicts: # src/util.rs
2 parents 59a840c + 22b94f3 commit 8bd7fd6

File tree

9 files changed

+530
-171
lines changed

9 files changed

+530
-171
lines changed

Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ description = "An easy-to-use library for HOTP and TOTP authentication"
44
repository = "https://github.com/tmthecoder/xotp"
55
readme = "README.md"
66
license = "MIT"
7-
keywords = ["otp", "hotp", "totp"]
7+
keywords = ["otp", "hotp", "totp", "otpauth"]
88
categories = ["cryptography"]
99
authors = ["Tejas Mehta <[email protected]>"]
10-
version = "0.1.0"
10+
version = "0.2.0"
1111
edition = "2021"
1212

1313
[features]
@@ -18,4 +18,5 @@ ffi = []
1818
hmac = "0.12.0"
1919
sha-1 = "0.10.0"
2020
sha2 = "0.10.1"
21-
base32 = "0.4.0"
21+
base32 = "0.4.0"
22+
url = "2.2.2"

README.md

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,51 +25,50 @@ fn get_otp_with_hotp() {
2525
let secret = "secret";
2626
let counter = 0;
2727
// Get a HOTP instance with a '&str' secret
28-
let hotp_str = HOTP::from_utf8(secret);
29-
// Get an otp with the given counter and digit count
30-
let otp_from_str = hotp_str.get_otp(counter, 6);
28+
let hotp_str = HOTP::default_from_utf8(secret);
29+
// Get an otp with the given counter
30+
let otp_from_str = hotp_str.get_otp(counter);
3131
println!("The otp from hotp_str: {}", otp_from_str);
32-
32+
3333
// Alternatively, get a HOTP instance with a '&[u8]' secret
34-
let hotp_bytes = HOTP::new(secret.as_bytes());
35-
// Get an otp with the given counter and digit count
36-
let otp_from_bytes = hotp_bytes.get_otp(counter, 6);
34+
let hotp_bytes = HOTP::new(secret.as_bytes(), 6);
35+
// Get an otp with the given counter
36+
let otp_from_bytes = hotp_bytes.get_otp(counter);
3737
println!("The otp from hotp_bytes: {}", otp_from_bytes);
38-
}
38+
}
3939
```
4040

4141
To use TOTP:
4242

4343
```rust
4444
use xotp::totp::TOTP;
45-
use xotp::util::MacDigest;
46-
// Only needed if using a non-SHA1 hash function
45+
use xotp::util::MacDigest; // Only needed if using a non-SHA1 hash function
4746
use std::time::{Duration, SystemTime, UNIX_EPOCH};
4847

4948
fn get_otp_with_totp() {
5049
let secret = "secret";
5150
let elapsed_seconds = SystemTime::now()
52-
.duration_since(SystemTime::UNIX_EPOCH)
51+
.duration_since(UNIX_EPOCH)
5352
.expect("Error getting time")
5453
.as_secs();
55-
// Get a TOTP instance a '&str' secret and default SHA1 Digest
56-
let totp_sha1_str = TOTP::from_utf8(secret);
57-
// Get an otp with the given counter and elapsed seconds
58-
let otp_sha1 = totp_sha1_str.get_otp(elapsed_seconds, 8);
54+
// Get a TOTP instance with an '&str' secret and default SHA1 Digest
55+
let totp_sha1_str = TOTP::default_from_utf8(secret);
56+
// Get an otp with the given counter and elapsed seconds
57+
let otp_sha1 = totp_sha1_str.get_otp(elapsed_seconds);
5958
println!("The otp from totp_sha1_str: {}", otp_sha1);
6059

61-
// Alternatively get a TOTP instance with a '&[u8]' secret
60+
// Alternatively get a TOTP instance with an '&[u8]' secret
6261
// and different digest (Sha256 or Sha512)
63-
let totp_sha256_bytes = TOTP::new_with_digest(
62+
let totp_sha256_bytes = TOTP::new(
6463
secret.as_bytes(),
65-
MacDigest::SHA256
64+
MacDigest::SHA256, // SHA256 algorithm
65+
8, // 8 digits
66+
60 // 60-second interval
6667
);
6768
// Get an otp with the given counter, time and other custom params
68-
let otp_sha256 = totp_sha256_bytes.get_otp_with_custom(
69+
let otp_sha256 = totp_sha256_bytes.get_otp_with_custom_time_start(
6970
elapsed_seconds,
70-
30, // A 60-second time step
7171
0, // Start time at unix epoch
72-
6 // 8-digit code
7372
);
7473
println!("The otp from totp_sha256_bytes: {}", otp_sha256);
7574
}

src/hotp.rs

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
// Implementation of the HOTP standard according to RFC4226 by Tejas Mehta
22

3-
use crate::util::{get_code, hash_generic, MacDigest};
4-
use base32::Alphabet;
3+
use crate::util::{base32_decode, get_code, hash_generic, MacDigest};
54

65
/// A HOTP Generator
76
///
8-
/// Follows the specification listed in [RFC4226]. Needs a secret on
9-
/// initialization, with other single generation-specific items being
10-
/// provided when [`HOTP::get_otp`] is called.
7+
/// Follows the specification listed in [RFC4226]. Needs a secret and a number of digits on initialization.
8+
/// The HOTP can then be generated using [`HOTP::get_otp`].
119
///
1210
/// # Example
1311
/// See the top-level README for an example of HOTP usage
@@ -23,61 +21,85 @@ pub struct HOTP {
2321
/// The secret key used in the HMAC process.
2422
///
2523
/// Often given as a Base32 key, which can be conveniently initialize using
26-
/// the [`HOTP::from_base32`] initializers
24+
/// the [`HOTP::from_base32`] constructors.
2725
secret: Vec<u8>,
26+
27+
/// The number of digits of the code generated.
28+
///
29+
/// This value defaults to 6 if not specified in a constructor.
30+
digits: u32,
2831
}
2932

3033
/// All initializer implementations for the [`HOTP`] struct.
31-
3234
impl HOTP {
3335
/// Creates a new HOTP instance with a byte-array representation
34-
/// of the secret
36+
/// of the secret and the number of digits.
3537
///
3638
/// Since only SHA1 was specified in the reference implementation and
37-
/// RFC specification, there's no need to initialize with a digest object
38-
pub fn new(secret: &[u8]) -> Self {
39+
/// RFC specification, there's no need to initialize with a digest object.
40+
pub fn new(secret: &[u8], digits: u32) -> Self {
3941
HOTP {
4042
secret: secret.to_vec(),
43+
digits,
4144
}
4245
}
4346

44-
/// Creates a new HOTP instance from a utf8-encoded string secret
45-
///
46-
/// Internally calls [`HOTP::new`] with the string's byte representation
47-
pub fn from_utf8(secret: &str) -> Self {
48-
HOTP::new(secret.as_bytes())
47+
/// Creates a new HOTP instance from an utf8-encoded string secret and the number of digits.
48+
pub fn new_from_utf8(secret: &str, digits: u32) -> Self {
49+
HOTP::new(secret.as_bytes(), digits)
4950
}
5051

51-
/// Creates a new HOTP instance from a base32-encoded string secret
52+
/// Creates a new HOTP instance from a base32-encoded string secret and the number of digits.
5253
///
53-
/// Internally calls [`HOTP::new`] after decoding the string
54+
/// # Panics
55+
/// This method panics if the provided string is not correctly base32 encoded.
56+
pub fn new_from_base32(secret: &str, digits: u32) -> Self {
57+
let decoded = base32_decode(secret).expect("Failed to decode base32 string");
58+
HOTP::new(&decoded, digits)
59+
}
60+
61+
/// Creates a new HOTP instance from a byte-array representation of the secret and
62+
/// a default number of 6 digits.
63+
pub fn default_from_secret(secret: &[u8]) -> Self {
64+
HOTP::new(secret, 6)
65+
}
66+
67+
/// Creates a new HOTP instance from an utf8-encoded string secret and a default number of 6 digits.
68+
pub fn default_from_utf8(secret: &str) -> Self {
69+
HOTP::new_from_utf8(secret, 6)
70+
}
71+
72+
/// Creates a new HOTP instance from a base32-encoded string secret and a default number of 6 digits.
5473
///
5574
/// # Panics
56-
/// This method panics if the provided string is not correctly base32
57-
/// encoded.
58-
pub fn from_base32(secret: &str) -> Self {
59-
let decoded = base32::decode(Alphabet::RFC4648 { padding: false }, secret)
60-
.expect("Failed to decode base32 string");
61-
HOTP::new(&decoded)
75+
/// This method panics if the provided string is not correctly base32 encoded.
76+
pub fn default_from_base32(secret: &str) -> Self {
77+
HOTP::new_from_base32(secret, 6)
6278
}
6379
}
6480

65-
/// All otp generation methods for the [`HOTP`] struct.
81+
impl HOTP {
82+
/// Gets the number of digits of the code.
83+
pub fn get_digits(&self) -> u32 {
84+
self.digits
85+
}
86+
}
6687

88+
/// All otp generation methods for the [`HOTP`] struct.
6789
impl HOTP {
68-
/// Generates and returns the HOTP value
90+
/// Generates and returns the HOTP value.
6991
///
70-
/// Uses the given counter value with the specified digit count
92+
/// Uses the given counter value.
7193
///
7294
/// # Panics
7395
/// This method panics if the hash's secret is incorrectly given.
74-
pub fn get_otp(&self, counter: u64, digits: u32) -> u32 {
96+
pub fn get_otp(&self, counter: u64) -> u32 {
7597
let hash = hash_generic(&counter.to_be_bytes(), &self.secret, &MacDigest::SHA1);
7698
let offset = (hash[hash.len() - 1] & 0xf) as usize;
7799
let bytes: [u8; 4] = hash[offset..offset + 4]
78100
.try_into()
79101
.expect("Failed byte get");
80102

81-
get_code(bytes, digits)
103+
get_code(bytes, self.digits)
82104
}
83105
}

src/lib.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
//! let secret = "secret";
1515
//! let counter = 0;
1616
//! // Get a HOTP instance with a '&str' secret
17-
//! let hotp_str = HOTP::from_utf8(secret);
18-
//! // Get an otp with the given counter and digit count
19-
//! let otp_from_str = hotp_str.get_otp(counter, 6);
17+
//! let hotp_str = HOTP::default_from_utf8(secret);
18+
//! // Get an otp with the given counter
19+
//! let otp_from_str = hotp_str.get_otp(counter);
2020
//! println!("The otp from hotp_str: {}", otp_from_str);
2121
//!
2222
//! // Alternatively, get a HOTP instance with a '&[u8]' secret
23-
//! let hotp_bytes = HOTP::new(secret.as_bytes());
24-
//! // Get an otp with the given counter and digit count
25-
//! let otp_from_bytes = hotp_bytes.get_otp(counter, 6);
23+
//! let hotp_bytes = HOTP::new(secret.as_bytes(), 6);
24+
//! // Get an otp with the given counter
25+
//! let otp_from_bytes = hotp_bytes.get_otp(counter);
2626
//! println!("The otp from hotp_bytes: {}", otp_from_bytes);
2727
//! }
2828
//! ```
@@ -41,23 +41,23 @@
4141
//! .expect("Error getting time")
4242
//! .as_secs();
4343
//! // Get a TOTP instance with an '&str' secret and default SHA1 Digest
44-
//! let totp_sha1_str = TOTP::from_utf8(secret);
44+
//! let totp_sha1_str = TOTP::default_from_utf8(secret);
4545
//! // Get an otp with the given counter and elapsed seconds
46-
//! let otp_sha1 = totp_sha1_str.get_otp(elapsed_seconds, 8);
46+
//! let otp_sha1 = totp_sha1_str.get_otp(elapsed_seconds);
4747
//! println!("The otp from totp_sha1_str: {}", otp_sha1);
4848
//!
4949
//! // Alternatively get a TOTP instance with an '&[u8]' secret
5050
//! // and different digest (Sha256 or Sha512)
51-
//! let totp_sha256_bytes = TOTP::new_with_digest(
51+
//! let totp_sha256_bytes = TOTP::new(
5252
//! secret.as_bytes(),
53-
//! MacDigest::SHA256
53+
//! MacDigest::SHA256, // SHA256 algorithm
54+
//! 8, // 8 digits
55+
//! 60 // 60-second interval
5456
//! );
5557
//! // Get an otp with the given counter, time and other custom params
56-
//! let otp_sha256 = totp_sha256_bytes.get_otp_with_custom(
58+
//! let otp_sha256 = totp_sha256_bytes.get_otp_with_custom_time_start(
5759
//! elapsed_seconds,
58-
//! 30, // A 60-second time step
5960
//! 0, // Start time at unix epoch
60-
//! 6 // 8-digit code
6161
//! );
6262
//! println!("The otp from totp_sha256_bytes: {}", otp_sha256);
6363
//! }

0 commit comments

Comments
 (0)