org.jboss.security.otp.HOTP Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.security.otp;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* Based on IETF RFC 4226 (http://tools.ietf.org/html/rfc4226)
* Code is derived from OATH HOTP algorithm
* @author [email protected]
* @since Sep 13, 2010
*/
public class HOTP
{
private static final int[] doubleDigits = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };
// 0 1 2 3 4 5 6 7 8
private static final int[] DIGITS_POWER = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
/**
* This method generates an OTP value for the given set of parameters.
*
* @param secret the shared secret
* @param movingFactor the counter, time, or other value that
* changes on a per use basis.
* @param codeDigits the number of digits in the OTP, not
* including the checksum, if any.
* @param addChecksum a flag that indicates if a checksum digit
* should be appended to the OTP.
* @param truncationOffset the offset into the MAC result to
* begin truncation. If this value is out of
* the range of 0 ... 15, then dynamic
* truncation will be used.
* Dynamic truncation is when the last 4
* bits of the last byte of the MAC are
* used to determine the start offset.
* @throws NoSuchAlgorithmException if no provider makes
* either HmacSHA1 or HMAC-SHA-1
* digest algorithms available.
* @throws InvalidKeyException
* The secret provided was not
* a valid HMAC-SHA-1 key.
*
* @return A numeric String in base 10 that includes
* {@link codeDigits} digits plus the optional checksum
* digit if requested.
*/
public static String generateOTP(byte[] secret,
long movingFactor,
int codeDigits,
boolean addChecksum,
int truncationOffset)
throws NoSuchAlgorithmException, InvalidKeyException
{
// 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 = hmac_sha1(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) + calcChecksum(otp, codeDigits);
}
result = Integer.toString(otp);
while (result.length() < digits)
{
result = "0" + result;
}
return result;
}
/**
* Calculates the checksum using the credit card algorithm.
* This algorithm has the advantage that it detects any single
* mistyped digit and any single transposition of
* adjacent digits.
*
* @param num the number to calculate the checksum for
* @param digits number of significant places in the number
*
* @return the checksum of num
*/
private 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 = doubleDigits[digit];
}
total += digit;
doubleDigit = !doubleDigit;
}
int result = total % 10;
if (result > 0)
{
result = 10 - result;
}
return result;
}
/**
* This method uses the JCE to provide the HMAC-SHA-1 algorithm.
* HMAC computes a Hashed Message Authentication Code and
* in this case SHA1 is the hash algorithm used.
*
* @param keyBytes the bytes to use for the HMAC-SHA-1 key
* @param text the message or text to be authenticated.
*
* @throws NoSuchAlgorithmException if no provider makes
* either HmacSHA1 or HMAC-SHA-1
* digest algorithms available.
* @throws InvalidKeyException
* The secret provided was not a valid HMAC-SHA-1 key.
*
*/
private static byte[] hmac_sha1( byte[] keyBytes, byte[] text )
throws NoSuchAlgorithmException, InvalidKeyException
{
Mac hmacSha1;
try
{
hmacSha1 = Mac.getInstance("HmacSHA1");
}
catch (NoSuchAlgorithmException nsae)
{
hmacSha1 = Mac.getInstance("HMAC-SHA-1");
}
SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
hmacSha1.init(macKey);
return hmacSha1.doFinal(text);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy