mirror of https://github.com/beemdevelopment/Aegis
commit
b875baacef
@ -0,0 +1,54 @@
|
|||||||
|
package com.beemdevelopment.aegis.crypto.otp;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.encoding.Hex;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
public class MOTP {
|
||||||
|
private final String _code;
|
||||||
|
private final int _digits;
|
||||||
|
|
||||||
|
private MOTP(String code, int digits) {
|
||||||
|
_code = code;
|
||||||
|
_digits = digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static MOTP generateOTP(byte[] secret, String algo, int digits, int period, String pin)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
|
||||||
|
return generateOTP(secret, algo, digits, period, pin, System.currentTimeMillis() / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static MOTP generateOTP(byte[] secret, String algo, int digits, int period, String pin, long time)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
|
||||||
|
long timeBasedCounter = time / period;
|
||||||
|
String secretAsString = Hex.encode(secret);
|
||||||
|
String toDigest = timeBasedCounter + secretAsString + pin;
|
||||||
|
String code = getDigest(algo, toDigest.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
return new MOTP(code, digits);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@NonNull
|
||||||
|
protected static String getDigest(String algo, byte[] toDigest) throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest md = MessageDigest.getInstance(algo);
|
||||||
|
byte[] digest = md.digest(toDigest);
|
||||||
|
|
||||||
|
return Hex.encode(digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return _code.substring(0, _digits);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package com.beemdevelopment.aegis.otp;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.crypto.otp.MOTP;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class MotpInfo extends TotpInfo {
|
||||||
|
public static final String ID = "motp";
|
||||||
|
public static final String SCHEME = "motp";
|
||||||
|
public static final String ALGORITHM = "MD5";
|
||||||
|
|
||||||
|
public static final int PERIOD = 10;
|
||||||
|
public static final int DIGITS = 6;
|
||||||
|
|
||||||
|
private String _pin;
|
||||||
|
|
||||||
|
public MotpInfo(@NonNull byte[] secret) throws OtpInfoException {
|
||||||
|
this(secret, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MotpInfo(byte[] secret, String pin) throws OtpInfoException {
|
||||||
|
super(secret, ALGORITHM, DIGITS, PERIOD);
|
||||||
|
setPin(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOtp() {
|
||||||
|
if (_pin == null) {
|
||||||
|
throw new IllegalStateException("PIN must be set before generating an OTP");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
MOTP otp = MOTP.generateOTP(getSecret(), getAlgorithm(false), getDigits(), getPeriod(), getPin());
|
||||||
|
return otp.toString();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOtp(long time) {
|
||||||
|
if (_pin == null) {
|
||||||
|
throw new IllegalStateException("PIN must be set before generating an OTP");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
MOTP otp = MOTP.generateOTP(getSecret(), getAlgorithm(false), getDigits(), getPeriod(), getPin(), time);
|
||||||
|
return otp.toString();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject toJson() {
|
||||||
|
JSONObject result = super.toJson();
|
||||||
|
try {
|
||||||
|
result.put("pin", getPin());
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getPin() {
|
||||||
|
return _pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPin(@NonNull String pin) {
|
||||||
|
this._pin = pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof MotpInfo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MotpInfo info = (MotpInfo) o;
|
||||||
|
return super.equals(o) && Objects.equals(getPin(), info.getPin());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package com.beemdevelopment.aegis.crypto.otp;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||||
|
import com.beemdevelopment.aegis.encoding.Hex;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
public class MOTPTest {
|
||||||
|
public static class Vector {
|
||||||
|
public String Secret;
|
||||||
|
public String OTP;
|
||||||
|
public String Pin;
|
||||||
|
public long Time;
|
||||||
|
|
||||||
|
public Vector(long time, String otp, String pin, String secret) {
|
||||||
|
Time = time;
|
||||||
|
OTP = otp;
|
||||||
|
Pin = pin;
|
||||||
|
Secret = secret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Vector[] VECTORS = {
|
||||||
|
new Vector(165892298, "e7d8b6", "1234", "e3152afee62599c8"),
|
||||||
|
new Vector(123456789, "4ebfb2", "1234", "e3152afee62599c8"),
|
||||||
|
new Vector(165954002 * 10, "ced7b1", "9999", "bbb1912bb5c515be"),
|
||||||
|
new Vector(165954002 * 10 + 2, "ced7b1", "9999", "bbb1912bb5c515be"),
|
||||||
|
new Vector(165953987 * 10, "1a14f8", "9999", "bbb1912bb5c515be"),
|
||||||
|
//should round down
|
||||||
|
new Vector(165953987 * 10 + 8, "1a14f8", "9999", "bbb1912bb5c515be")
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOutputCode() throws NoSuchAlgorithmException, EncodingException {
|
||||||
|
for (Vector vector : VECTORS) {
|
||||||
|
MOTP otp = MOTP.generateOTP(Hex.decode(vector.Secret), "MD5", 6, 10, vector.Pin, vector.Time);
|
||||||
|
assertEquals(vector.OTP, otp.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDigest() throws NoSuchAlgorithmException {
|
||||||
|
assertEquals("355938cfe3b73a624297591972d27c01",
|
||||||
|
MOTP.getDigest("MD5", "BOB".getBytes(StandardCharsets.UTF_8)));
|
||||||
|
assertEquals("16d7a4fca7442dda3ad93c9a726597e4",
|
||||||
|
MOTP.getDigest("MD5", "test1234".getBytes(StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.beemdevelopment.aegis.otp;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.crypto.otp.MOTPTest;
|
||||||
|
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||||
|
import com.beemdevelopment.aegis.encoding.Hex;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class MotpInfoTest {
|
||||||
|
@Test
|
||||||
|
public void testMotpInfoOtp() throws OtpInfoException, EncodingException {
|
||||||
|
for (MOTPTest.Vector vector : MOTPTest.VECTORS) {
|
||||||
|
MotpInfo info = new MotpInfo(Hex.decode(vector.Secret), vector.Pin);
|
||||||
|
assertEquals(vector.OTP, info.getOtp(vector.Time));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue