GordianPasswordLockRecipe.java
/*
* GordianKnot: Security Suite
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.gordianknot.impl.core.lock;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigest;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestFactory;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestType;
import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianBadCredentialsException;
import io.github.tonywasher.joceanus.gordianknot.api.lock.GordianPasswordLockSpec;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMac;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacFactory;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpec;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianDataConverter;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianIdManager;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianPersonalisation;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianPersonalisation.GordianPersonalId;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keyset.GordianCoreKeySet;
import io.github.tonywasher.joceanus.gordianknot.impl.core.keyset.GordianCoreKeySetFactory;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
/**
* Class for assembling/disassembling PasswordLocks.
*/
public final class GordianPasswordLockRecipe {
/**
* Hash margins.
*/
private static final int HASH_MARGIN = 4;
/**
* The PasswordLockSpec.
*/
private final GordianPasswordLockSpec theLockSpec;
/**
* The Recipe.
*/
private final byte[] theRecipe;
/**
* The Salt.
*/
private final byte[] theSalt;
/**
* The Initialisation Vector.
*/
private final byte[] theInitVector;
/**
* The Hash.
*/
private byte[] theHashBytes;
/**
* The Payload.
*/
private final byte[] thePayload;
/**
* The Lock Parameters.
*/
private final GordianPasswordLockParams theParams;
/**
* Constructor for random choices.
*
* @param pFactory the factory
* @param pLockSpec the passwordLockSpec
*/
GordianPasswordLockRecipe(final GordianBaseFactory pFactory,
final GordianPasswordLockSpec pLockSpec) {
/* Access the secureRandom */
final SecureRandom myRandom = pFactory.getRandomSource().getRandom();
/* Create the Salt vector */
theSalt = new byte[GordianLockData.SALTLEN];
myRandom.nextBytes(theSalt);
/* Calculate the initVector */
final GordianPersonalisation myPersonal = pFactory.getPersonalisation();
theInitVector = myPersonal.adjustIV(theSalt);
/* Allocate new set of parameters */
theParams = new GordianPasswordLockParams(pFactory);
theRecipe = theParams.getRecipe();
theLockSpec = pLockSpec;
theHashBytes = null;
thePayload = null;
}
/**
* Constructor for external form parse.
*
* @param pFactory the factory
* @param pPassLength the password length
* @param pLockASN1 the lockASN1
*/
GordianPasswordLockRecipe(final GordianBaseFactory pFactory,
final int pPassLength,
final GordianPasswordLockASN1 pLockASN1) {
/* Parse the ASN1 external form */
final byte[] myHashBytes = pLockASN1.getHashBytes();
theLockSpec = pLockASN1.getLockSpec();
thePayload = pLockASN1.getPayload();
/* Create the byte arrays */
theRecipe = new byte[GordianLockData.RECIPELEN];
theSalt = new byte[GordianLockData.SALTLEN];
theHashBytes = new byte[GordianLockData.HASHLEN];
/* Determine offset position */
int myOffSet = Math.max(pPassLength, HASH_MARGIN);
myOffSet = Math.min(myOffSet, GordianLockData.HASHLEN
- HASH_MARGIN);
/* Copy Data into buffers */
System.arraycopy(myHashBytes, 0, theHashBytes, 0, myOffSet);
System.arraycopy(myHashBytes, myOffSet, theRecipe, 0, GordianLockData.RECIPELEN);
System.arraycopy(myHashBytes, myOffSet
+ GordianLockData.RECIPELEN, theSalt, 0, GordianLockData.SALTLEN);
System.arraycopy(myHashBytes, myOffSet
+ GordianLockData.RECIPELEN
+ GordianLockData.SALTLEN, theHashBytes, myOffSet, GordianLockData.HASHLEN
- myOffSet);
/* Calculate the initVector */
final GordianPersonalisation myPersonal = pFactory.getPersonalisation();
theInitVector = myPersonal.adjustIV(theSalt);
/* Allocate new set of parameters */
theParams = new GordianPasswordLockParams(pFactory, theRecipe);
}
/**
* Obtain the payload.
*
* @return the payLoad
*/
byte[] getPayload() {
return thePayload;
}
/**
* Build lockBytes for hash and password length.
*
* @param pPassLength the password length
* @param pPayload the payload
* @return the lockBytes
*/
GordianPasswordLockASN1 buildLockASN1(final int pPassLength,
final byte[] pPayload) {
/* Allocate the new buffer */
final int myHashLen = theHashBytes.length;
final int myLen = GordianLockData.RECIPELEN
+ GordianLockData.SALTLEN
+ myHashLen;
final byte[] myBuffer = new byte[myLen];
/* Determine offset position */
int myOffSet = Math.max(pPassLength, HASH_MARGIN);
myOffSet = Math.min(myOffSet, myHashLen
- HASH_MARGIN);
/* Copy Data into buffer */
System.arraycopy(theHashBytes, 0, myBuffer, 0, myOffSet);
System.arraycopy(theRecipe, 0, myBuffer, myOffSet, GordianLockData.RECIPELEN);
System.arraycopy(theSalt, 0, myBuffer, myOffSet
+ GordianLockData.RECIPELEN, GordianLockData.SALTLEN);
System.arraycopy(theHashBytes, myOffSet, myBuffer, myOffSet
+ GordianLockData.RECIPELEN
+ GordianLockData.SALTLEN, myHashLen
- myOffSet);
/* Build the ASN1 form */
return new GordianPasswordLockASN1(theLockSpec, myBuffer, pPayload);
}
/**
* Process password.
*
* @param pFactory the factory
* @param pPassword the password for the keys
* @return the locking KeySet
* @throws GordianException on error
*/
GordianCoreKeySet processPassword(final GordianBaseFactory pFactory,
final byte[] pPassword) throws GordianException {
/* Obtain configuration details */
final GordianPersonalisation myPersonal = pFactory.getPersonalisation();
final int iIterations = theLockSpec.getNumIterations();
final int iFinal = theParams.getAdjustment()
+ iIterations;
/* Create a byte array of the iterations */
final byte[] myLoops = GordianDataConverter.integerToByteArray(iFinal);
/* Access factories */
final GordianDigestFactory myDigests = pFactory.getDigestFactory();
final GordianMacFactory myMacs = pFactory.getMacFactory();
/* Create the primeMac */
GordianMacSpec myMacSpec = GordianMacSpecBuilder.hMac(theParams.getPrimeDigest());
final GordianMac myPrimeMac = myMacs.createMac(myMacSpec);
myPrimeMac.initKeyBytes(pPassword);
/* Create the alternateMac */
myMacSpec = GordianMacSpecBuilder.hMac(theParams.getSecondaryDigest());
final GordianMac mySecondaryMac = myMacs.createMac(myMacSpec);
mySecondaryMac.initKeyBytes(pPassword);
/* Create the alternateMac */
myMacSpec = GordianMacSpecBuilder.hMac(theParams.getTertiaryDigest());
final GordianMac myTertiaryMac = myMacs.createMac(myMacSpec);
myTertiaryMac.initKeyBytes(pPassword);
/* Create the secretMac */
myMacSpec = GordianMacSpecBuilder.hMac(new GordianDigestSpec(theParams.getSecretDigest(), GordianLength.LEN_512));
final GordianMac mySecretMac = myMacs.createMac(myMacSpec);
mySecretMac.initKeyBytes(pPassword);
/* Initialise hash bytes and counter */
final byte[] myPrimeBytes = new byte[myPrimeMac.getMacSize()];
final byte[] mySecondaryBytes = new byte[mySecondaryMac.getMacSize()];
final byte[] myTertiaryBytes = new byte[myTertiaryMac.getMacSize()];
final byte[] mySecretBytes = new byte[mySecretMac.getMacSize()];
final byte[] myPrimeHash = new byte[myPrimeMac.getMacSize()];
final byte[] mySecondaryHash = new byte[mySecondaryMac.getMacSize()];
final byte[] myTertiaryHash = new byte[myTertiaryMac.getMacSize()];
final byte[] mySecretHash = new byte[mySecretMac.getMacSize()];
/* Access final digest */
final GordianDigestSpec myDigestSpec = new GordianDigestSpec(theParams.getExternalDigest(), GordianLength.LEN_512);
final GordianDigest myDigest = myDigests.createDigest(myDigestSpec);
/* Initialise the hash input values as the salt bytes */
final byte[] mySaltBytes = theInitVector;
byte[] myPrimeInput = mySaltBytes;
byte[] mySecondaryInput = mySaltBytes;
byte[] myTertiaryInput = mySaltBytes;
byte[] mySecretInput = mySaltBytes;
/* Protect from exceptions */
try {
/* Update each Hash with the personalisation */
myPersonal.updateMac(myPrimeMac);
myPersonal.updateMac(mySecondaryMac);
myPersonal.updateMac(myTertiaryMac);
myPersonal.updateMac(mySecretMac);
/* Update each Hash with the loops */
myPrimeMac.update(myLoops);
mySecondaryMac.update(myLoops);
myTertiaryMac.update(myLoops);
mySecretMac.update(myLoops);
/* Loop through the iterations */
for (int iPass = 0; iPass < iFinal; iPass++) {
/* Update the prime Mac */
myPrimeMac.update(mySecondaryInput);
myPrimeMac.update(myTertiaryInput);
/* Update the secondary Mac */
mySecondaryMac.update(myPrimeInput);
mySecondaryMac.update(myTertiaryInput);
/* Update the tertiary Mac */
myTertiaryMac.update(myPrimeInput);
myTertiaryMac.update(mySecondaryInput);
/* Update the secret Mac */
mySecretMac.update(mySecretInput);
mySecretMac.update(myPrimeInput);
mySecretMac.update(mySecondaryInput);
mySecretMac.update(myTertiaryInput);
/* Update inputs */
myPrimeInput = myPrimeHash;
mySecondaryInput = mySecondaryHash;
myTertiaryInput = myTertiaryHash;
mySecretInput = mySecretHash;
/* Recalculate hashes and combine them */
myPrimeMac.finish(myPrimeHash, 0);
GordianPersonalisation.buildHashResult(myPrimeBytes, myPrimeHash);
mySecondaryMac.finish(mySecondaryHash, 0);
GordianPersonalisation.buildHashResult(mySecondaryBytes, mySecondaryHash);
myTertiaryMac.finish(myTertiaryHash, 0);
GordianPersonalisation.buildHashResult(myTertiaryBytes, myTertiaryHash);
mySecretMac.finish(mySecretHash, 0);
GordianPersonalisation.buildHashResult(mySecretBytes, mySecretHash);
}
/* Combine the Primary, Secondary and Tertiary bytes to form the external hash */
myDigest.update(myPrimeBytes);
myDigest.update(mySecondaryBytes);
myDigest.update(myTertiaryBytes);
final byte[] myHashBytes = myDigest.finish();
/* If we are resolving the lock, check the hash */
if (theHashBytes != null
&& !Arrays.equals(theHashBytes, myHashBytes)) {
/* Fail the password attempt */
throw new GordianBadCredentialsException("Invalid Password");
}
theHashBytes = myHashBytes;
/* Create the Key Set */
final GordianCoreKeySet myKeySet = ((GordianCoreKeySetFactory) pFactory.getKeySetFactory()).createKeySet(theLockSpec.getKeySetSpec());
myKeySet.buildFromSecret(mySecretBytes);
/* Return to caller */
return myKeySet;
/* Clear intermediate arrays */
} finally {
Arrays.fill(myPrimeHash, (byte) 0);
Arrays.fill(myPrimeBytes, (byte) 0);
Arrays.fill(mySecondaryHash, (byte) 0);
Arrays.fill(mySecondaryBytes, (byte) 0);
Arrays.fill(myTertiaryHash, (byte) 0);
Arrays.fill(myTertiaryBytes, (byte) 0);
Arrays.fill(mySecretHash, (byte) 0);
Arrays.fill(mySecretBytes, (byte) 0);
}
}
/**
* The parameters class.
*/
private static final class GordianPasswordLockParams {
/**
* The Recipe.
*/
private final byte[] theRecipe;
/**
* The secret hMac type.
*/
private final GordianDigestType theSecretDigest;
/**
* The hMac types.
*/
private final GordianDigestType[] theDigests;
/**
* The external Digest type.
*/
private final GordianDigestType theExternalDigest;
/**
* The Adjustment.
*/
private final int theAdjust;
/**
* Construct the parameters from random.
*
* @param pFactory the factory
*/
GordianPasswordLockParams(final GordianBaseFactory pFactory) {
/* Obtain Id manager and random */
final GordianIdManager myManager = pFactory.getIdManager();
final GordianPersonalisation myPersonal = pFactory.getPersonalisation();
final SecureRandom myRandom = pFactory.getRandomSource().getRandom();
/* Generate recipe and derive digestTypes */
final int mySeed = myRandom.nextInt();
theRecipe = GordianDataConverter.integerToByteArray(mySeed);
final Random mySeededRandom = myPersonal.getSeededRandom(GordianPersonalId.LOCKRANDOM, theRecipe);
theSecretDigest = myManager.deriveLockSecretTypeFromSeed(mySeededRandom);
theDigests = myManager.deriveLockDigestTypesFromSeed(mySeededRandom, GordianLockData.NUM_DIGESTS);
theExternalDigest = myManager.deriveExternalDigestTypeFromSeed(mySeededRandom);
/* Derive random adjustment value */
theAdjust = mySeededRandom.nextInt(GordianDataConverter.NYBBLE_MASK + 1);
}
/**
* Construct the parameters from recipe.
*
* @param pFactory the factory
* @param pRecipe the recipe bytes
*/
GordianPasswordLockParams(final GordianBaseFactory pFactory,
final byte[] pRecipe) {
/* Obtain Id manager */
final GordianIdManager myManager = pFactory.getIdManager();
final GordianPersonalisation myPersonal = pFactory.getPersonalisation();
/* Store recipe and derive digestTypes */
theRecipe = pRecipe;
final Random mySeededRandom = myPersonal.getSeededRandom(GordianPersonalId.LOCKRANDOM, theRecipe);
theSecretDigest = myManager.deriveLockSecretTypeFromSeed(mySeededRandom);
theDigests = myManager.deriveLockDigestTypesFromSeed(mySeededRandom, GordianLockData.NUM_DIGESTS);
theExternalDigest = myManager.deriveExternalDigestTypeFromSeed(mySeededRandom);
/* Derive random adjustment value */
theAdjust = mySeededRandom.nextInt(GordianDataConverter.NYBBLE_MASK + 1);
}
/**
* Obtain the Recipe.
*
* @return the recipe
*/
byte[] getRecipe() {
return theRecipe;
}
/**
* Obtain the Prime Digest type.
*
* @return the digest type
*/
GordianDigestType getPrimeDigest() {
return theDigests[0];
}
/**
* Obtain the Secondary Digest type.
*
* @return the digest type
*/
GordianDigestType getSecondaryDigest() {
return theDigests[1];
}
/**
* Obtain the Tertiary Digest type.
*
* @return the digest type
*/
GordianDigestType getTertiaryDigest() {
return theDigests[2];
}
/**
* Obtain the Secret Digest type.
*
* @return the digest type
*/
GordianDigestType getSecretDigest() {
return theSecretDigest;
}
/**
* Obtain the external Digest type.
*
* @return the digest type
*/
GordianDigestType getExternalDigest() {
return theExternalDigest;
}
/**
* Obtain the Adjustment.
*
* @return the adjustment
*/
int getAdjustment() {
return theAdjust;
}
}
}