Added weird shit... idk if it works, Tests don't work yet
This commit is contained in:
parent
866f5aac0b
commit
def350d1da
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,4 +2,4 @@
|
||||
/target/
|
||||
.classpath
|
||||
/.settings
|
||||
|
||||
/src/test/
|
||||
|
@ -1,15 +1,26 @@
|
||||
package com.cringe_studios.cringe_authenticator_library;
|
||||
|
||||
public interface OTP {
|
||||
public abstract class OTP {
|
||||
//TOTP
|
||||
//HOTP
|
||||
|
||||
protected String secret;
|
||||
protected OTPType type;
|
||||
protected OTPAlgorithm algorithm;
|
||||
protected long digits;
|
||||
protected int digits;
|
||||
protected long counter;
|
||||
protected long period;
|
||||
protected boolean checksum;
|
||||
|
||||
protected OTP(OTPType nType, String nSecret, OTPAlgorithm nAlgorithm, int nDigits, long nCounter, long nPeriodInSeconds, boolean nChecksum) {
|
||||
this.secret = nSecret;
|
||||
type = nType;
|
||||
algorithm = nAlgorithm;
|
||||
digits = nDigits;
|
||||
counter = nCounter;
|
||||
period = nPeriodInSeconds;
|
||||
checksum = nChecksum;
|
||||
}
|
||||
|
||||
//secret: required
|
||||
//algorithm: optional (default SHA1)
|
||||
@ -17,37 +28,39 @@ public interface OTP {
|
||||
//OTPType: required
|
||||
//period: optional (default 30)
|
||||
//counter: required if hotp
|
||||
public static OTP createNewOTP(OTPType type, String secret, OTPAlgorithm algorithm, long digits, long counter, long periodInSeconds) {
|
||||
return null;
|
||||
public static OTP createNewOTP(OTPType type, String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) {
|
||||
return type.instance(secret, algorithm, digits, counter, periodInSeconds, checksum);
|
||||
}
|
||||
|
||||
public long getNextDueTime();
|
||||
public abstract long getNextDueTime();
|
||||
|
||||
public String getPin();
|
||||
public abstract String getPin();
|
||||
|
||||
public String updatePin();
|
||||
|
||||
default public String getSecret() {
|
||||
public String getSecret() {
|
||||
return this.secret;
|
||||
}
|
||||
|
||||
default public OTPType getOTPType() {
|
||||
public OTPType getOTPType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
default public OTPAlgorithm getAlgorithm() {
|
||||
public OTPAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
default public long getDigits() {
|
||||
public int getDigits() {
|
||||
return this.digits;
|
||||
}
|
||||
|
||||
default public long getCounter() {
|
||||
public long getCounter() {
|
||||
return this.counter;
|
||||
}
|
||||
|
||||
default public long getPeriod() {
|
||||
public long getPeriod() {
|
||||
return this.period;
|
||||
}
|
||||
|
||||
public boolean hasChecksum() {
|
||||
return checksum;
|
||||
}
|
||||
}
|
@ -1,13 +1,35 @@
|
||||
package com.cringe_studios.cringe_authenticator_library;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public enum OTPAlgorithm{
|
||||
SHA1,
|
||||
SHA224,
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512;
|
||||
SHA1("HmacSHA1"),
|
||||
SHA224("HmacSHA224"),
|
||||
SHA256("HmacSHA256"),
|
||||
SHA384("HmacSHA384"),
|
||||
SHA512("HmacSHA512");
|
||||
|
||||
private String javaName;
|
||||
|
||||
private OTPAlgorithm(String nJavaName) {
|
||||
javaName = nJavaName;
|
||||
}
|
||||
|
||||
public String getJavaName() {
|
||||
return this.name();
|
||||
return javaName;
|
||||
}
|
||||
|
||||
public byte[] hash(byte[] keyBytes, byte[] text) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||
Mac hmac;
|
||||
hmac = Mac.getInstance(this.getJavaName());
|
||||
|
||||
SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
|
||||
|
||||
hmac.init(macKey);
|
||||
return hmac.doFinal(text);
|
||||
}
|
||||
}
|
@ -6,11 +6,21 @@ public enum OTPType {
|
||||
|
||||
private String friendlyName;
|
||||
|
||||
private OTPType(String nFriendlyName){
|
||||
private OTPType(String nFriendlyName) {
|
||||
friendlyName = nFriendlyName;
|
||||
}
|
||||
|
||||
public String getFriendlyName() {
|
||||
return friendlyName;
|
||||
}
|
||||
|
||||
public OTP instance(String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) {
|
||||
switch(this) {
|
||||
case HOTP:
|
||||
return new com.cringe_studios.cringe_authenticator_library.impl.HOTP(secret, algorithm, digits, counter, periodInSeconds, checksum);
|
||||
case TOTP:
|
||||
return new com.cringe_studios.cringe_authenticator_library.impl.TOTP(secret, algorithm, digits, counter, periodInSeconds, checksum);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,28 @@
|
||||
package com.cringe_studios.cringe_authenticator_library.impl;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.MacSpi;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.xml.crypto.dsig.spec.HMACParameterSpec;
|
||||
|
||||
import com.cringe_studios.cringe_authenticator_library.OTP;
|
||||
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
|
||||
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
||||
|
||||
//https://datatracker.ietf.org/doc/html/rfc4226
|
||||
public class HOTP implements OTP{
|
||||
public class HOTP extends OTP {
|
||||
|
||||
protected static final int[] DIGITS_POWER
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
= { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
|
||||
|
||||
public HOTP(String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) {
|
||||
super(OTPType.HOTP, secret, algorithm, digits, counter, periodInSeconds, checksum);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextDueTime() {
|
||||
@ -20,10 +35,41 @@ public class HOTP implements OTP{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String updatePin() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
public String generateOTP(int truncationOffset) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
boolean addChecksum = this.hasChecksum();
|
||||
byte[] secret = this.getSecret().getBytes(StandardCharsets.US_ASCII);
|
||||
int codeDigits = this.getDigits();
|
||||
long movingFactor = this.getCounter();
|
||||
|
||||
// put movingFactor value into text byte array
|
||||
String result = null;
|
||||
int digits = addChecksum ? (codeDigits + 1) : codeDigits;
|
||||
byte[] text = new byte[8];
|
||||
for (int i = text.length - 1; i >= 0; i--) {
|
||||
text[i] = (byte) (movingFactor & 0xff);
|
||||
movingFactor >>= 8;
|
||||
}
|
||||
|
||||
// compute hmac hash
|
||||
byte[] hash = this.getAlgorithm().hash(secret, text);
|
||||
|
||||
// put selected bytes into result int
|
||||
int offset = hash[hash.length - 1] & 0xf;
|
||||
if ((0 <= truncationOffset) && (truncationOffset < (hash.length - 4))) {
|
||||
offset = truncationOffset;
|
||||
}
|
||||
int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)
|
||||
| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
|
||||
|
||||
int otp = binary % DIGITS_POWER[codeDigits];
|
||||
if (addChecksum) {
|
||||
otp = (otp * 10) + HOTPChecksumProvider.calcChecksum(otp, codeDigits);
|
||||
}
|
||||
result = Integer.toString(otp);
|
||||
while (result.length() < digits) {
|
||||
result = "0" + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
package com.cringe_studios.cringe_authenticator_library.impl;
|
||||
|
||||
public final class HOTPChecksumProvider {
|
||||
// These are used to calculate the check-sum digits. 0 1 2 3 4 5 6 7 8 9
|
||||
private static final int[] DOUBLE_DIGITS = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };
|
||||
|
||||
private HOTPChecksumProvider() {}
|
||||
|
||||
public static int calcChecksum(long num, int digits) {
|
||||
boolean doubleDigit = true;
|
||||
int total = 0;
|
||||
while (0 < digits--) {
|
||||
int digit = (int) (num % 10);
|
||||
num /= 10;
|
||||
if (doubleDigit) {
|
||||
digit = DOUBLE_DIGITS[digit];
|
||||
}
|
||||
total += digit;
|
||||
doubleDigit = !doubleDigit;
|
||||
}
|
||||
int result = total % 10;
|
||||
if (result > 0) {
|
||||
result = 10 - result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -1,16 +1,30 @@
|
||||
package com.cringe_studios.cringe_authenticator_library.impl;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
|
||||
import com.cringe_studios.cringe_authenticator_library.OTP;
|
||||
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
|
||||
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
||||
|
||||
//https://datatracker.ietf.org/doc/html/rfc6238
|
||||
public class TOTP implements OTP{
|
||||
public class TOTP extends HOTP {
|
||||
|
||||
public TOTP(String secret, OTPAlgorithm algorithm, int digits, long counter, long periodInSeconds, boolean checksum) {
|
||||
super(secret, algorithm, digits, counter, periodInSeconds, checksum);
|
||||
this.type = OTPType.TOTP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextDueTime() {
|
||||
// TODO Auto-generated method stub
|
||||
//return this.period System.time;
|
||||
return 0;
|
||||
// e.g. current time: 20 seconds & current period: 30 seconds -> next due time:
|
||||
// 30 seconds
|
||||
return (this.getCounter() + 1) * this.getPeriod();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -20,9 +34,49 @@ public class TOTP implements OTP{
|
||||
}
|
||||
|
||||
@Override
|
||||
public String updatePin() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
public long getCounter() {
|
||||
return Instant.now().getEpochSecond() / this.getPeriod();
|
||||
|
||||
}
|
||||
|
||||
private static byte[] hexStr2Bytes(String hex) {
|
||||
// Adding one byte to get the right conversion
|
||||
// Values starting with "0" can be converted
|
||||
byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
|
||||
|
||||
// Copy all the REAL bytes, not the "first"
|
||||
byte[] ret = new byte[bArray.length - 1];
|
||||
for (int i = 0; i < ret.length; i++)
|
||||
ret[i] = bArray[i + 1];
|
||||
return ret;
|
||||
}
|
||||
|
||||
public String getPinAt(String key, String time) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||
int codeDigits = this.getDigits();
|
||||
|
||||
// Using the counter
|
||||
// First 8 bytes are for the movingFactor
|
||||
// Compliant with base RFC 4226 (HOTP)
|
||||
while (time.length() < 16)
|
||||
time = "0" + time;
|
||||
|
||||
// Get the HEX in a Byte[]
|
||||
byte[] msg = hexStr2Bytes(time);
|
||||
byte[] k = hexStr2Bytes(key);
|
||||
byte[] hash = this.getAlgorithm().hash(k, msg);
|
||||
|
||||
// put selected bytes into result int
|
||||
int offset = hash[hash.length - 1] & 0xf;
|
||||
|
||||
int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)
|
||||
| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
|
||||
|
||||
int otp = binary % DIGITS_POWER[codeDigits];
|
||||
|
||||
String result = Integer.toString(otp);
|
||||
while (result.length() < codeDigits) {
|
||||
result = "0" + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user