Java tutorial
/** * The MIT License * Copyright (c) 2015 Population Register Centre * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package fi.vm.kapa.identification.service; import org.springframework.security.crypto.codec.Hex; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.math.BigInteger; import java.security.SecureRandom; /** * This class is intended as a general purpose tool for calculating * checksum based on HMAC algorithm. This is mainly used to verify * the validity of incoming requests but it can be used to all other * purposes where time limited checksum is needed. */ public class PhaseIdService { private final static String ALPHANUMBERIC = "^[a-z0-9]*$"; private final static int TID_LENGTH = 26; private final static int PID_LENGTH = 64; private int timeInterval; private Mac hmacCalc; public PhaseIdService(String sharedSecret, int timeInterval, String hmacAlgorithm) throws Exception { this.timeInterval = timeInterval; hmacCalc = Mac.getInstance(hmacAlgorithm); SecretKeySpec keySpec = new SecretKeySpec(sharedSecret.getBytes(), hmacAlgorithm); hmacCalc.init(keySpec); } /** * Creates new standard length token ID that is used to calculate * the phase ID. * * @return New token ID as alphanumeric string */ public String nextTokenId() { String token = new BigInteger(130, new SecureRandom()).toString(32); while (token.length() != TID_LENGTH) { token = new BigInteger(130, new SecureRandom()).toString(32); } return token; } /** * Validates token ID and phase ID that they must be alphanumeric hash strings * with correct string lengths and that they must not be in the used IDs list * * @param tid token ID to be verified * @param pid phase ID to be verified * @return Boolean value, true if both values are OK */ public boolean validateTidAndPid(String tid, String pid) { return (tid.matches(ALPHANUMBERIC) && pid.matches(ALPHANUMBERIC) && tid.length() == TID_LENGTH && pid.length() == PID_LENGTH); } /** * Generates new phase ID from the given step counter and token ID. * * @param tokenId Simple string token which can be generated by random * @param stepCounter The step counter value to which the phase ID must match * @return New phase ID value as alphanumeric string * @throws Exception */ public String newPhaseId(String tokenId, String stepCounter) throws Exception { long sysTime = System.currentTimeMillis(); int time = (int) (sysTime / (timeInterval * 1000)); return generateHmacString(tokenId, stepCounter, time); } /** * Verifies the given phase ID against the given token ID and step counter values. * Tries to match the current time frame and current +-1 time frame to the given phase ID * as the time slot might have just changed when it was generated. * * @param phaseId The value which should be generated from the other parameters * @param tokenId The token ID received in the request * @param stepCounter Step counter of the current step, this must be defined in the calling service * @return Boolean value if the verification succeeded or not * @throws Exception */ synchronized public boolean verifyPhaseId(String phaseId, String tokenId, String stepCounter) throws Exception { long sysTime = System.currentTimeMillis(); int time = (int) (sysTime / (timeInterval * 1000)); /* This allows 3 x time interval for counting the HMAC checksum and if anyone of them succeeds, * then the next phase ID is generated from current time, this has to be quite strict to prevent * possible attacks! Note that this step is dependant on the phase number so each server has to * know its own phases! */ return (generateHmacString(tokenId, stepCounter, time).equals(phaseId) || generateHmacString(tokenId, stepCounter, (time - 1)).equals(phaseId) || generateHmacString(tokenId, stepCounter, (time + 1)).equals(phaseId)); } private String generateHmacString(String tokenId, String stepCounter, int time) { return String.valueOf(Hex .encode(hmacCalc.doFinal(("tid=" + tokenId + ";pid=" + stepCounter + ";time=" + time).getBytes()))); } }