diff --git a/.gitignore b/.gitignore index 051ea92..0cf15c3 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,6 @@ aws.properties .vagrant/ Vagrantfile + +.gradle/ +build/ diff --git a/README.md b/README.md index e10955c..ef5fed2 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # Argon2 Java implementation -This is a pure Java implementation of [Argon2](https://github.com/P-H-C/phc-winner-argon2). It targets Java 6. +This is a pure Java implementation of [Argon2](https://github.com/P-H-C/phc-winner-argon2). It targets Java 8. -Many parts are just taken from the C code and converted to Java syntax - there shouldn't be any major performance bottlenecks anymore, but speed can't keep up quite with the C implementation. +Many parts are just taken from the C code and converted to Java syntax. Runtime overhead compared to the C implementation is within 20 percent. -Fair warning: this code is not well tested and not reviewed. It is not ready for production, use it on your own risk. Same applies for underlying Blake2b implementation. +Fair warning: This code is neither excessively tested nor reviewed. Use it at your own risk. Same applies to the underlying Blake2b cryptographic hash implementation. ## Benchmarks -[Rplots.pdf](benchmarks/Rplots.pdf) +Old benchmark before memory optimization of the Java implementation: [Rplots.pdf](benchmarks/Rplots.pdf) Red is java, green the reference implementation and blue the optimized implementation with SSE-instructions - run on a i7-7700HQ with 16GB RAM. @@ -33,67 +33,59 @@ Clone and install it: ``` - -## Usage +## Usage -``` -// Read password +### Create encoded hash when a user is setting it's password +```java char[] password = readPassword(); - -// Generate salt String salt = generateRandomSalt(); // Hash password - Builder pattern -String hash = Argon2Factory.create() - .setIterations(2) - .setMemory(14) - .setParallelism(1) - .hash(password, salt); - -``` +String encodedHash = Argon2Factory.create() + .setIterations(2) + .setMemory(14) + .setParallelism(1) + .hash(password, salt). + .asEncoded(); + +storeToDataBase(encodedHash); ``` -% java -jar argon2-0.1.jar -usage: argon2 salt [-d | -i | -id] [-e | -r] [-h] [-k | -m ] [-l - ] [-p ] [-t ] --d Use Argon2d instead of Argon2i --e Output only encoded hash --h Print usage --i Use Argon2i (this is the default) --id Use Argon2id instead of Argon2i --k Sets the memory usage of N KiB (default 2^12) --l Sets hash output length to N bytes (default 32) --m Sets the memory usage of 2^N KiB (default 12) --p Sets parallelism to N (default 1) --r Output only the raw bytes of the hash --t Sets the number of iterations to N (default = 3) -Password is read from stdin + +### Verify that a user entered a correct password +```java +String password = readFromUser(); +String encodedHash = readFromDatabase(); + +boolean match = Argon2().checkHash(encodedHash, password); +if (match) { + loginUser(); +} else { + printInvalidUsernameOrPassword(); +} ``` ## Blake2b -The [blake2b Java implementation](https://github.com/alphazero/Blake2b) of [@alphazero](https://github.com/alphazero/) is used. Because no artifact is available on maven central, I copied the relevant class file into this project - for the sake of simplicity. +The [blake2b Java implementation](https://github.com/alphazero/Blake2b) from [@alphazero](https://github.com/alphazero/) is used. Because no artifact is available on Maven Central the relevant class file is embedded in this project. ## License [MIT](https://opensource.org/licenses/MIT) ## TODO * Verification -* Encoded result * Performance + * Can we get closer to the C implementation? * in Block.xorBlock(), the JIT actually already creates vector operations * Fix the build * Build library without commons-dependency, build executable with * maven central * add gradle support -* Add Tests -* .. - +* Add more Tests ## Contact [@andreas1327250](https://github.com/andreas1327250) diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..39198db --- /dev/null +++ b/build.gradle @@ -0,0 +1,44 @@ +plugins { + id 'java' +} + +group 'rip.deadcode.argon2' +version '0.1-SNAPSHOT' + +repositories { + mavenCentral() + maven { + url = "https://dl.bintray.com/minebreaker/test" + } +} + +dependencies { + testCompile( + 'org.junit.jupiter:junit-jupiter-api:5.3.2', + 'de.mkammerer:argon2-jvm:2.5', + 'com.google.truth:truth:0.42', +// 'com.google.truth.extensions:truth-java8-extension:0.42', +// 'org.mockito:mockito-core:2.23.4', +// 'rip.deadcode:izvestia:0.2', +// 'com.google.jimfs:jimfs:1.1' + ) + testRuntime( + 'org.junit.jupiter:junit-jupiter-engine:5.3.2', + 'de.mkammerer:argon2-jvm:2.5' + ) +} + +test { + useJUnitPlatform() +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +compileJava { + options.encoding = 'UTF-8' +} + +compileTestJava { + options.encoding = 'UTF-8' +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1b2b07c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 01ed6b1..0000000 --- a/pom.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - 4.0.0 - - at.gadermaier - argon2 - 0.1 - - - - commons-cli - commons-cli - 1.3 - compile - - - - junit - junit - ${junit.version} - test - - - - org.hamcrest - hamcrest-all - ${hamcrest.version} - test - - - - - - - - maven-compiler-plugin - 3.0 - - 1.6 - 1.6 - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - at.gadermaier.argon2.Main - - - - - jar-with-dependencies - - false - jar-with-dependencies - - - - make-assembly - package - - single - - - - - - - - - - maven-surefire-plugin - 2.19.1 - - alphabetical - - - - - - - - - UTF-8 - 4.12 - 1.3 - - - \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..1f08d54 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'argon2' + diff --git a/src/main/java/at/gadermaier/argon2/Argon2.java b/src/main/java/at/gadermaier/argon2/Argon2.java index 7c0bbc8..ffe57c7 100644 --- a/src/main/java/at/gadermaier/argon2/Argon2.java +++ b/src/main/java/at/gadermaier/argon2/Argon2.java @@ -6,18 +6,23 @@ import at.gadermaier.argon2.model.Argon2Type; import at.gadermaier.argon2.model.Instance; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; import java.nio.charset.Charset; import java.util.Arrays; +import java.util.Base64; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import static at.gadermaier.argon2.Constants.Defaults.*; +import static at.gadermaier.argon2.Util.clearArray; +import static at.gadermaier.argon2.Util.toByteArray; +import static at.gadermaier.argon2.model.Argon2Type.*; + public class Argon2 { private byte[] output; private int outputLength; // -l N - private double duration; private byte[] password; private byte[] salt; @@ -32,12 +37,15 @@ public class Argon2 { private Argon2Type type; private boolean clearMemory = true; - private Charset charset = Charset.forName("UTF-8"); + private static Charset charset = Charset.forName( "UTF-8" ); + + private ExecutorService executor; - private boolean encodedOnly = false; - private boolean rawOnly = false; + private static final class ExecutorHolder { + private static final ExecutorService executor = Executors.newCachedThreadPool(); + } - Argon2() { + private Argon2() { this.lanes = LANES_DEF; this.outputLength = OUTLEN_DEF; this.memory = 1 << LOG_M_COST_DEF; @@ -46,119 +54,141 @@ public class Argon2 { this.type = TYPE_DEF; } - private static byte[] toByteArray(char[] chars, Charset charset) { - assert chars != null; + public static Argon2 create() { + return new Argon2(); + } + + private static Argon2 createFromEncoded( String encoded ) { + + String[] parts = encoded.split( "\\$" ); + Argon2Type type = parts[1].endsWith( "id" ) ? Argon2id : + parts[1].endsWith( "i" ) ? Argon2i : + parts[1].endsWith( "d" ) ? Argon2d : null; + boolean hasVersion = !parts[2].startsWith( "m=" ); + String version = hasVersion ? parts[2].split( "=" )[1] : "16"; + String[] paramParts = ( hasVersion ? parts[3] : parts[2] ).split( "," ); + String m = paramParts[0].split( "=" )[1]; + String t = paramParts[1].split( "=" )[1]; + String p = paramParts[2].split( "=" )[1]; + + String saltEncoded = hasVersion ? parts[4] : parts[3]; + byte[] salt = Base64.getDecoder().decode( saltEncoded ); + String hashEncoded = hasVersion ? parts[5] : parts[4]; + byte[] hash = Base64.getDecoder().decode( hashEncoded ); + + Argon2 instance = new Argon2(); + instance.setType( type ); + instance.setVersion( Integer.parseInt( version ) ); + instance.setMemoryInKiB( Integer.parseInt( m ) ); + instance.setIterations( Integer.parseInt( t ) ); + instance.setParallelism( Integer.parseInt( p ) ); + instance.setSalt( salt ); + instance.setOutput( hash ); + return instance; + } + + public static boolean checkHash( String encoded, byte[] password ) { + + Argon2 argon2 = Argon2.createFromEncoded( encoded ); + byte[] expectedHash = Arrays.copyOf( argon2.getOutput(), argon2.getOutput().length ); + argon2.setPassword( password ); + Argon2Result result = argon2.hash(); + + return Arrays.equals( result.asByte(), expectedHash ); + } + + public static boolean checkHash( String encoded, String password ) { + return checkHash( encoded, password.getBytes( charset ) ); + } + + public static boolean checkHash( String encoded, String password, Charset charset ) { + return checkHash( encoded, password.getBytes( charset ) ); + } + + public Argon2Result hash( byte[] password, byte[] salt ) { + setPassword( password ); + setSalt( salt ); - CharBuffer charBuffer = CharBuffer.wrap(chars); - ByteBuffer byteBuffer = charset.encode(charBuffer); - byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), - byteBuffer.position(), byteBuffer.limit()); - Arrays.fill(byteBuffer.array(), (byte) 0); - return bytes; + return hash(); } - public String hash(byte[] password, byte[] salt){ - setPassword(password); - setSalt(salt); + public Argon2Result hash( char[] password, String salt, Charset charset ) { + setPassword( toByteArray( password, charset ) ); + setSalt( salt.getBytes( charset ) ); return hash(); } - public String hash(char[] password, String salt){ - setPassword(password); - setSalt(salt); + public Argon2Result hash( String password, String salt ) { + return hash( password, salt, charset ); + } + + public Argon2Result hash( String password, String salt, Charset charset ) { + setPassword( password.getBytes( charset ) ); + setSalt( salt.getBytes( charset ) ); return hash(); } - public String hash() { + public Argon2Result hash() { try { + byte[] keepSalt = Arrays.copyOf( salt, salt.length ); argon2_hash(); - return getOutputString(); - }finally { + return new Argon2Result( type, version, memory, iterations, lanes, keepSalt, output ); + } finally { clear(); } } private void argon2_hash() { - Validation.validateInput(this); - long start = System.nanoTime(); + if ( executor == null ) { + executor = ExecutorHolder.executor; + } - Instance instance = new Instance(this); + Validation.validateInput( this ); - Initialize.initialize(instance, this); - FillMemory.fillMemoryBlocks(instance); - Finalize.finalize(instance, this); + Instance instance = new Instance( this ); - duration = (System.nanoTime() - start) / 1000000000.0; + Initialize.initialize( instance, this ); + FillMemory.fillMemoryBlocks( instance, executor ); + Finalize.finalize( instance, this ); } public void clear() { - if(password != null) - Arrays.fill(password, 0, password.length-1, (byte)0); - - if(salt != null) - Arrays.fill(salt, 0, salt.length-1, (byte)0); - - if(secret != null) - Arrays.fill(secret, 0, secret.length-1, (byte)0); - - if(additional != null) - Arrays.fill(additional, 0, additional.length-1, (byte)0); - } - - void printSummary(){ - if(encodedOnly) - System.out.println(getEncoded()); - else if(rawOnly) - System.out.println(getOutputString()); - else { - System.out.println("Type:\t\t" + type); - System.out.println("Iterations:\t" + iterations); - System.out.println("Memory:\t\t" + memory + " KiB"); - System.out.println("Parallelism:\t" + lanes); - System.out.println("Hash:\t\t" + getOutputString()); - System.out.println("Encoded:\t " + getEncoded()); - System.out.println(duration + " seconds"); - } + clearArray( password ); + clearArray( salt ); + clearArray( secret ); + clearArray( additional ); } - public Argon2 setMemoryInKiB(int memory) { + public Argon2 setMemoryInKiB( int memory ) { this.memory = memory; return this; } - public Argon2 setParallelism(int parallelism){ + public Argon2 setParallelism( int parallelism ) { this.lanes = parallelism; return this; } - public Argon2 setPassword(char[] password) { - return setPassword(toByteArray(password, charset)); - } - - public Argon2 setSalt(String salt) { - return setSalt(salt.getBytes(charset)); - } - public byte[] getOutput() { return output; } - public void setOutput(byte[] finalResult) { + public void setOutput( byte[] finalResult ) { this.output = finalResult; } public String getOutputString() { - return Util.bytesToHexString(output); + return Util.bytesToHexString( output ); } public int getOutputLength() { return outputLength; } - public Argon2 setOutputLength(int outputLength) { + public Argon2 setOutputLength( int outputLength ) { this.outputLength = outputLength; return this; } @@ -167,7 +197,7 @@ public byte[] getPassword() { return password; } - public Argon2 setPassword(byte[] password) { + public Argon2 setPassword( byte[] password ) { this.password = password; return this; } @@ -180,7 +210,7 @@ public byte[] getSalt() { return salt; } - public Argon2 setSalt(byte[] salt) { + public Argon2 setSalt( byte[] salt ) { this.salt = salt; return this; } @@ -193,7 +223,7 @@ public byte[] getSecret() { return secret; } - public Argon2 setSecret(byte[] secret) { + public Argon2 setSecret( byte[] secret ) { this.secret = secret; return this; } @@ -206,20 +236,20 @@ public byte[] getAdditional() { return additional; } - public Argon2 setAdditional(byte[] additional) { + public Argon2 setAdditional( byte[] additional ) { this.additional = additional; return this; } public int getAdditionalLength() { - return additional != null ? additional.length : 0; + return additional != null ? additional.length : 0; } public int getIterations() { return iterations; } - public Argon2 setIterations(int iterations) { + public Argon2 setIterations( int iterations ) { this.iterations = iterations; return this; } @@ -228,7 +258,7 @@ public int getMemory() { return memory; } - public Argon2 setMemory(int memory) { + public Argon2 setMemory( int memory ) { this.memory = 1 << memory; return this; } @@ -241,7 +271,7 @@ public int getVersion() { return version; } - public Argon2 setVersion(int version) { + public Argon2 setVersion( int version ) { this.version = version; return this; } @@ -250,7 +280,7 @@ public Argon2Type getType() { return type; } - public Argon2 setType(Argon2Type type) { + public Argon2 setType( Argon2Type type ) { this.type = type; return this; } @@ -259,23 +289,16 @@ public boolean isClearMemory() { return clearMemory; } - public void setClearMemory(boolean clearMemory) { + public void setClearMemory( boolean clearMemory ) { this.clearMemory = clearMemory; } - public Charset getCharset() { - return charset; - } - - public void setEncodedOnly(boolean encodedOnly) { - this.encodedOnly = encodedOnly; - } - - public void setRawOnly(boolean rawOnly) { - this.rawOnly = rawOnly; + public Executor getExecutor() { + return executor; } - public String getEncoded() { - return ""; //TODO + public Argon2 setExecutor( ExecutorService executor ) { + this.executor = executor; + return this; } } diff --git a/src/main/java/at/gadermaier/argon2/Argon2ArgumentFactory.java b/src/main/java/at/gadermaier/argon2/Argon2ArgumentFactory.java deleted file mode 100644 index 8cc9a7f..0000000 --- a/src/main/java/at/gadermaier/argon2/Argon2ArgumentFactory.java +++ /dev/null @@ -1,162 +0,0 @@ -package at.gadermaier.argon2; - -import at.gadermaier.argon2.model.Argon2Type; -import org.apache.commons.cli.*; - -import static java.lang.Integer.parseInt; - -public class Argon2ArgumentFactory { - - static Argon2 parseArguments(String[] args){ - - Options options = buildOptions(); - - CommandLineParser parser = new DefaultParser(); - HelpFormatter formatter = new HelpFormatter(); - CommandLine commandLine = null; - - try { - commandLine = parser.parse(options, args); - - if(commandLine.getArgs().length != 1) - throw new ParseException("no password or salt"); - - return createArgon2(commandLine); - } catch (ParseException e) { - formatter.printHelp("argon2 salt", options,true); - System.out.println("Password is read from stdin"); - - bailOut(); - } - - // not reachable - return null; - } - - private static Argon2 createArgon2(CommandLine commandLine) throws ParseException { - Argon2 argon2 = new Argon2(); - String salt = commandLine.getArgs()[0]; - - argon2.setSalt(salt); - - if(commandLine.hasOption("h")) - throw new ParseException("usage"); - - if(commandLine.hasOption("t")){ - argon2.setIterations(parseInt(commandLine.getOptionValue("t"))); - } - - if(commandLine.hasOption("p")){ - argon2.setParallelism(parseInt(commandLine.getOptionValue("p"))); - } - - if(commandLine.hasOption("m")){ - argon2.setMemory(parseInt(commandLine.getOptionValue("m"))); - }else if(commandLine.hasOption("k")){ - int k = parseInt(commandLine.getOptionValue("k")); - if(k % 4*argon2.getLanes() != 0) - throw new ParseException("k must be a multiple of p*4"); - argon2.setMemoryInKiB(k); - } - - - if(commandLine.hasOption("e")){ - argon2.setEncodedOnly(true); - }else if(commandLine.hasOption("r")){ - argon2.setRawOnly(true); - } - - if(commandLine.hasOption("i")){ - argon2.setType(Argon2Type.Argon2i); - }else if(commandLine.hasOption("d")){ - argon2.setType(Argon2Type.Argon2d); - }else if(commandLine.hasOption("id")){ - argon2.setType(Argon2Type.Argon2id); - } - - if (commandLine.hasOption(("l"))) { - argon2.setOutputLength(parseInt(commandLine.getOptionValue("l"))); - } - - if(commandLine.hasOption("v")){ - int version = parseInt(commandLine.getOptionValue("v")); - if (!(version == 10 || version == 13)) { - bailOut("wrong version"); - } - argon2.setVersion(version); - } - - return argon2; - } - - private static Options buildOptions(){ - Options options = new Options(); - Option option; - - OptionGroup optionGroup = new OptionGroup(); - - option = new Option("i", null, false, "Use Argon2i (this is the default)"); - optionGroup.addOption(option); - option = new Option("d", null, false, "Use Argon2d instead of Argon2i"); - optionGroup.addOption(option); - option = new Option("id", null, false, "Use Argon2id instead of Argon2i"); - optionGroup.addOption(option); - - options.addOptionGroup(optionGroup); - - - option = new Option("t", null, true, "Sets the number of iterations to N (default = 3)"); - option.setArgName("N"); - option.setType(Integer.class); - options.addOption(option); - - optionGroup = new OptionGroup(); - - option = new Option("m", null, true, "Sets the memory usage of 2^N KiB (default 12)"); - option.setArgName("N"); - option.setType(Integer.class); - optionGroup.addOption(option); - - option = new Option("k", null, true, "Sets the memory usage of N KiB (default 2^12)"); - option.setArgName("N"); - option.setType(Integer.class); - optionGroup.addOption(option); - - options.addOptionGroup(optionGroup); - - - option = new Option("p", null, true, "Sets parallelism to N (default 1)"); - option.setArgName("N"); - option.setType(Integer.class); - options.addOption(option); - - option = new Option("l", null, true, "Sets hash output length to N bytes (default 32)"); - option.setArgName("N"); - option.setType(Integer.class); - options.addOption(option); - - - optionGroup = new OptionGroup(); - - option = new Option("e", null, false, "Output only encoded hash"); - optionGroup.addOption(option); - option = new Option("r", null, false, "Output only the raw bytes of the hash"); - optionGroup.addOption(option); - - options.addOptionGroup(optionGroup); - - option = new Option("h", null, false, "Print usage"); - options.addOption(option); - - return options; - } - - private static void bailOut(String message){ - System.out.println(message); - bailOut(); - } - - private static void bailOut(){ - System.exit(1); - } -} diff --git a/src/main/java/at/gadermaier/argon2/Argon2Factory.java b/src/main/java/at/gadermaier/argon2/Argon2Factory.java deleted file mode 100644 index 3f7f51f..0000000 --- a/src/main/java/at/gadermaier/argon2/Argon2Factory.java +++ /dev/null @@ -1,7 +0,0 @@ -package at.gadermaier.argon2; - -public class Argon2Factory { - public static Argon2 create(){ - return new Argon2(); - } -} diff --git a/src/main/java/at/gadermaier/argon2/Argon2Result.java b/src/main/java/at/gadermaier/argon2/Argon2Result.java new file mode 100644 index 0000000..fa5bcca --- /dev/null +++ b/src/main/java/at/gadermaier/argon2/Argon2Result.java @@ -0,0 +1,66 @@ +package at.gadermaier.argon2; + +import at.gadermaier.argon2.model.Argon2Type; + +import java.util.Arrays; +import java.util.Base64; + +import static at.gadermaier.argon2.Util.bytesToHexString; + + +public final class Argon2Result { + + private final Argon2Type type; + private final int version; + private final int memoryInKb; + private final int iteration; + private final int paralellism; + private final byte[] salt; + private final byte[] hash; + + Argon2Result( + Argon2Type type, int version, int memoryInKiB, int iteration, int parallelism, byte[] salt, byte[] hash ) { + + this.type = type; + this.version = version; + this.memoryInKb = memoryInKiB; + this.iteration = iteration; + this.paralellism = parallelism; + this.salt = Arrays.copyOf( salt, salt.length ); + this.hash = Arrays.copyOf( hash, hash.length ); + } + + public byte[] asByte() { + return Arrays.copyOf( hash, hash.length ); + } + + public String asString() { + return bytesToHexString( hash ); + } + + public String asEncoded() { + + String type = this.type.equals( Argon2Type.Argon2i ) ? "i" : + this.type.equals( Argon2Type.Argon2d ) ? "d" : + this.type.equals( Argon2Type.Argon2id ) ? "id" : null; + String salt = Base64.getEncoder().withoutPadding().encodeToString( this.salt ); + String hash = Base64.getEncoder().withoutPadding().encodeToString( this.hash ); + + return new StringBuilder() + .append( "$argon2" ) + .append( type ) + .append( "$v=" ) + .append( version ) + .append( "$m=" ) + .append( memoryInKb ) + .append( ",t=" ) + .append( iteration ) + .append( ",p=" ) + .append( paralellism ) + .append( "$" ) + .append( salt ) + .append( "$" ) + .append( hash ) + .toString(); + } +} diff --git a/src/main/java/at/gadermaier/argon2/Benchmark.java b/src/main/java/at/gadermaier/argon2/Benchmark.java deleted file mode 100644 index 429e44f..0000000 --- a/src/main/java/at/gadermaier/argon2/Benchmark.java +++ /dev/null @@ -1,88 +0,0 @@ -package at.gadermaier.argon2; - -import at.gadermaier.argon2.model.Argon2Type; - -import static at.gadermaier.argon2.Constants.Defaults.ARGON2_VERSION_NUMBER; -import static at.gadermaier.argon2.model.Argon2Type.Argon2d; -import static at.gadermaier.argon2.model.Argon2Type.Argon2i; - -public class Benchmark { - - public static void main(String[] args) { - if (args.length > 0) - benchmark(Integer.parseInt(args[0])); - else - benchmark(18); - } - - /* - * Benchmarks Argon2 with salt length 16, password length 16, t_cost 1, - * and different m_cost and threads - */ - private static void benchmark(int maxMemory) { - - System.out.println("threads;memory;seconds"); - - int inlen = 16; - int outlen = 16; - - int t_cost = 3; - int m_cost; - int[] thread_test = new int[]{1, 2, 4, 8}; - Argon2Type[] types = new Argon2Type[]{Argon2i, Argon2d}; - - byte[] pwdBytes = new byte[inlen]; - byte[] saltBytes = new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - - //warmup jit - Argon2Factory.create() - .setIterations(10) - .setParallelism(4) - .hash("password".toCharArray(), "saltsalt"); - - for (m_cost = 10; m_cost <= maxMemory; m_cost++) { - for (int i = 0; i < 4; i++) { - - - double run_time = 0; - int thread_n = thread_test[i]; - - int runs = 3; - run(outlen, t_cost, m_cost, types, pwdBytes, saltBytes, run_time, thread_n, runs); - System.gc(); - } - } - } - - private static void run(int outlen, int t_cost, int m_cost, Argon2Type[] types, byte[] pwdBytes, byte[] saltBytes, double run_time, int thread_n, int runs) { - for(int averageIndex=0; averageIndex= 0; i--) { + public static long readLong(byte[] buffer, int offset) { + long result = 0; + for (int i = offset + 7; i >= offset; i--) { result <<= 8; - result |= (b[i] & 0xFF); + result |= (buffer[i] & 0xFF); } return result; - } + } - public static byte[] intToLittleEndianBytes(int a) { - byte[] result = new byte[4]; - result[0] = (byte) (a & 0xFF); - result[1] = (byte) ((a >> 8) & 0xFF); - result[2] = (byte) ((a >> 16) & 0xFF); - result[3] = (byte) ((a >> 24) & 0xFF); - return result; - } + public static int writeInt(byte[] result, int offset, int value) { + result[offset++] = byte0(value); + result[offset++] = byte1(value); + result[offset++] = byte2(value); + result[offset++] = byte3(value); + return offset; + } + + public static byte byte0(int a) { + return (byte) (a & 0xFF); + } - public static byte[] longToLittleEndianBytes(long a) { + public static byte byte1(int a) { + return (byte) ((a >> 8) & 0xFF); + } + + public static byte byte2(int a) { + return (byte) ((a >> 16) & 0xFF); + } + + public static byte byte3(int a) { + return (byte) ((a >> 24) & 0xFF); + } + + public static byte[] longToLittleEndianBytes(long value) { byte[] result = new byte[8]; - result[0] = (byte) (a & 0xFF); - result[1] = (byte) ((a >> 8) & 0xFF); - result[2] = (byte) ((a >> 16) & 0xFF); - result[3] = (byte) ((a >> 24) & 0xFF); - result[4] = (byte) ((a >> 32) & 0xFF); - result[5] = (byte) ((a >> 40) & 0xFF); - result[6] = (byte) ((a >> 48) & 0xFF); - result[7] = (byte) ((a >> 56) & 0xFF); + writeLong(result, 0, value); return result; } + public static int writeLong(byte[] result, int offset, long value) { + result[offset++] = byte0(value); + result[offset++] = byte1(value); + result[offset++] = byte2(value); + result[offset++] = byte3(value); + result[offset++] = byte4(value); + result[offset++] = byte5(value); + result[offset++] = byte6(value); + result[offset++] = byte7(value); + return offset; + } + + private static byte byte0(long value) { + return (byte) (value & 0xFF); + } + + private static byte byte1(long value) { + return (byte) ((value >> 8) & 0xFF); + } + + private static byte byte2(long value) { + return (byte) ((value >> 16) & 0xFF); + } + + private static byte byte3(long value) { + return (byte) ((value >> 24) & 0xFF); + } + + private static byte byte4(long value) { + return (byte) ((value >> 32) & 0xFF); + } + + private static byte byte5(long value) { + return (byte) ((value >> 40) & 0xFF); + } + + private static byte byte6(long value) { + return (byte) ((value >> 48) & 0xFF); + } + + private static byte byte7(long value) { + return (byte) ((value >> 56) & 0xFF); + } + public static long intToLong(int x){ - byte[] intBytes = intToLittleEndianBytes(x); - byte[] bytes = new byte[8]; - System.arraycopy(intBytes, 0, bytes, 0, 4); - return littleEndianBytesToLong(bytes); + return x & 0xFFFFFFFFL; } -} + static byte[] toByteArray(char[] chars, Charset charset) { + assert chars != null; + + CharBuffer charBuffer = CharBuffer.wrap( chars); + ByteBuffer byteBuffer = charset.encode( charBuffer); + byte[] bytes = Arrays.copyOfRange( byteBuffer.array(), + byteBuffer.position(), byteBuffer.limit()); + Arrays.fill(byteBuffer.array(), (byte) 0); + return bytes; + } + static void clearArray(byte[] arr) { + if (arr != null) + Arrays.fill( arr, 0, arr.length - 1, (byte)0 ); + } +} diff --git a/src/main/java/at/gadermaier/argon2/algorithm/FillBlock.java b/src/main/java/at/gadermaier/argon2/algorithm/FillBlock.java index 76b414b..8eb64aa 100644 --- a/src/main/java/at/gadermaier/argon2/algorithm/FillBlock.java +++ b/src/main/java/at/gadermaier/argon2/algorithm/FillBlock.java @@ -3,42 +3,28 @@ import at.gadermaier.argon2.model.Block; class FillBlock { + + private Block R = new Block(); + private Block Z = new Block(); - static void fillBlock(Block X, Block Y, Block currentBlock, boolean withXor) { - - Block R = new Block(); - Block Z = new Block(); - + void fillBlock(Block X, Block Y, Block currentBlock, boolean withXor) { R.xor(X, Y); - Z.copyBlock(R); + Z.copyFrom(R); /* Apply Blake2 on columns of 64-bit words: (0,1,...,15) , then (16,17,..31)... finally (112,113,...127) */ - for (int i = 0; i < 8; i++) { - - Functions.roundFunction(Z, - 16 * i, 16 * i + 1, 16 * i + 2, - 16 * i + 3, 16 * i + 4, 16 * i + 5, - 16 * i + 6, 16 * i + 7, 16 * i + 8, - 16 * i + 9, 16 * i + 10, 16 * i + 11, - 16 * i + 12, 16 * i + 13, 16 * i + 14, - 16 * i + 15 - ); + for (int i = 0; i < 128; i+=16) { + Z.roundFunction( + i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7, + i + 8, i + 9, i + 10, i + 11, i + 12, i + 13, i + 14, i + 15); } /* Apply Blake2 on rows of 64-bit words: (0,1,16,17,...112,113), then (2,3,18,19,...,114,115).. finally (14,15,30,31,...,126,127) */ - for (int i = 0; i < 8; i++) { - - Functions.roundFunction(Z, - 2 * i, 2 * i + 1, 2 * i + 16, - 2 * i + 17, 2 * i + 32, 2 * i + 33, - 2 * i + 48, 2 * i + 49, 2 * i + 64, - 2 * i + 65, 2 * i + 80, 2 * i + 81, - 2 * i + 96, 2 * i + 97, 2 * i + 112, - 2 * i + 113 - ); - + for (int i = 0; i < 16; i+=2) { + Z.roundFunction( + i, i + 1, i + 16, i + 17, i + 32, i + 33, i + 48, i + 49, + i + 64, i + 65, i + 80, i + 81, i + 96, i + 97, i + 112, i + 113); } if (withXor) { diff --git a/src/main/java/at/gadermaier/argon2/algorithm/FillMemory.java b/src/main/java/at/gadermaier/argon2/algorithm/FillMemory.java index 53b424c..9c51dda 100644 --- a/src/main/java/at/gadermaier/argon2/algorithm/FillMemory.java +++ b/src/main/java/at/gadermaier/argon2/algorithm/FillMemory.java @@ -1,65 +1,62 @@ package at.gadermaier.argon2.algorithm; -import at.gadermaier.argon2.model.Instance; -import at.gadermaier.argon2.model.Position; +import static at.gadermaier.argon2.Constants.*; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; -import static at.gadermaier.argon2.Constants.ARGON2_SYNC_POINTS; +import at.gadermaier.argon2.model.Instance; +import at.gadermaier.argon2.model.Position; public class FillMemory { - public static void fillMemoryBlocks(Instance instance) { + public static void fillMemoryBlocks(Instance instance, ExecutorService executor) { if (instance.getLanes() == 1) { fillMemoryBlockSingleThreaded(instance); } else { - fillMemoryBlockMultiThreaded(instance); + fillMemoryBlockMultiThreaded(instance, executor); } } private static void fillMemoryBlockSingleThreaded(Instance instance) { + FillSegment worker = new FillSegment(); for (int i = 0; i < instance.getIterations(); i++) { for (int j = 0; j < ARGON2_SYNC_POINTS; j++) { Position position = new Position(i, 0, j, 0); - FillSegment.fillSegment(instance, position); + worker.fillSegment(instance, position); } } } - private static void fillMemoryBlockMultiThreaded(final Instance instance) { + private static void fillMemoryBlockMultiThreaded(final Instance instance, ExecutorService executor) { - ExecutorService service = Executors.newFixedThreadPool(instance.getLanes()); - List> futures = new ArrayList>(); + Future[] futures = new Future[instance.getLanes()]; + FillSegment[] workers = new FillSegment[instance.getLanes()]; + for (int k = 0; k < instance.getLanes(); k++) { + workers[k] = new FillSegment(); + } for (int i = 0; i < instance.getIterations(); i++) { for (int j = 0; j < ARGON2_SYNC_POINTS; j++) { for (int k = 0; k < instance.getLanes(); k++) { - final Position position = new Position(i, k, j, 0); - - Future future = service.submit(new Runnable() { + final FillSegment worker = workers[k]; + + futures[k] = executor.submit(new Runnable() { @Override public void run() { - FillSegment.fillSegment(instance, position); + worker.fillSegment(instance, position); } }); - - futures.add(future); } joinThreads(instance, futures); } } - - service.shutdownNow(); } - private static void joinThreads(Instance instance, List> futures) { + private static void joinThreads(Instance instance, Future[] futures) { try { for (Future f : futures) { f.get(); diff --git a/src/main/java/at/gadermaier/argon2/algorithm/FillSegment.java b/src/main/java/at/gadermaier/argon2/algorithm/FillSegment.java index 4ce31c7..c359e19 100644 --- a/src/main/java/at/gadermaier/argon2/algorithm/FillSegment.java +++ b/src/main/java/at/gadermaier/argon2/algorithm/FillSegment.java @@ -1,5 +1,7 @@ package at.gadermaier.argon2.algorithm; +import static at.gadermaier.argon2.Constants.*; + import at.gadermaier.argon2.Constants; import at.gadermaier.argon2.Util; import at.gadermaier.argon2.model.Argon2Type; @@ -7,13 +9,17 @@ import at.gadermaier.argon2.model.Instance; import at.gadermaier.argon2.model.Position; -import static at.gadermaier.argon2.Constants.ARGON2_ADDRESSES_IN_BLOCK; -import static at.gadermaier.argon2.Constants.ARGON2_VERSION_10; - class FillSegment { - static void fillSegment(Instance instance, Position position) { + private final FillBlock filler = new FillBlock(); + + private final Block _addressBlock = new Block(); + private final Block _zeroBlock = new Block(); + private final Block _inputBlock = new Block(); + + + void fillSegment(Instance instance, Position position) { Block addressBlock = null, inputBlock = null, zeroBlock = null; @@ -23,9 +29,13 @@ static void fillSegment(Instance instance, Position position) { int prevOffset = getPrevOffset(instance, currentOffset); if (dataIndependentAddressing) { - addressBlock = new Block(); - zeroBlock = new Block(); - inputBlock = new Block(); + addressBlock = _addressBlock; + zeroBlock = _zeroBlock; + inputBlock = _inputBlock; + + addressBlock.clear(); + zeroBlock.clear(); + inputBlock.clear(); initAddressBlocks(instance, position, zeroBlock, inputBlock, addressBlock); } @@ -38,12 +48,12 @@ static void fillSegment(Instance instance, Position position) { int refColumn = getRefColumn(instance, position, pseudoRandom, refLane == position.lane); /* 2 Creating a new block */ - Block prevBlock = instance.memory[prevOffset]; - Block refBlock = instance.memory[((instance.getLaneLength()) * refLane + refColumn)]; - Block currentBlock = instance.memory[currentOffset]; + Block prevBlock = instance.memory(prevOffset); + Block refBlock = instance.memory(((instance.getLaneLength()) * refLane + refColumn)); + Block currentBlock = instance.memory(currentOffset); boolean withXor = isWithXor(instance, position); - FillBlock.fillBlock(prevBlock, refBlock, currentBlock, withXor); + filler.fillBlock(prevBlock, refBlock, currentBlock, withXor); } } @@ -55,11 +65,11 @@ private static boolean isDataIndependentAddressing(Instance instance, Position p ); } - private static void initAddressBlocks(Instance instance, Position position, Block zeroBlock, Block inputBlock, Block addressBlock) { + private void initAddressBlocks(Instance instance, Position position, Block zeroBlock, Block inputBlock, Block addressBlock) { inputBlock.v[0] = Util.intToLong(position.pass); inputBlock.v[1] = Util.intToLong(position.lane); inputBlock.v[2] = Util.intToLong(position.slice); - inputBlock.v[3] = Util.intToLong(instance.memory.length); + inputBlock.v[3] = Util.intToLong(instance.memoryLength()); inputBlock.v[4] = Util.intToLong(instance.getIterations()); inputBlock.v[5] = Util.intToLong(instance.getType().ordinal()); @@ -98,22 +108,22 @@ private static int getStartingIndex(Position position) { } } - private static void nextAddresses(Block zeroBlock, Block inputBlock, Block addressBlock) { + private void nextAddresses(Block zeroBlock, Block inputBlock, Block addressBlock) { inputBlock.v[6]++; - FillBlock.fillBlock(zeroBlock, inputBlock, addressBlock, false); - FillBlock.fillBlock(zeroBlock, addressBlock, addressBlock, false); + filler.fillBlock(zeroBlock, inputBlock, addressBlock, false); + filler.fillBlock(zeroBlock, addressBlock, addressBlock, false); } /* 1.2 Computing the index of the reference block */ /* 1.2.1 Taking pseudo-random value from the previous block */ - private static long getPseudoRandom(Instance instance, Position position, Block addressBlock, Block inputBlock, Block zeroBlock, int prevOffset, boolean dataIndependentAddressing) { + private long getPseudoRandom(Instance instance, Position position, Block addressBlock, Block inputBlock, Block zeroBlock, int prevOffset, boolean dataIndependentAddressing) { if (dataIndependentAddressing) { if (position.index % ARGON2_ADDRESSES_IN_BLOCK == 0) { nextAddresses(zeroBlock, inputBlock, addressBlock); } return addressBlock.v[position.index % ARGON2_ADDRESSES_IN_BLOCK]; } else { - return instance.memory[prevOffset].v[0]; + return instance.memory(prevOffset).v[0]; } } diff --git a/src/main/java/at/gadermaier/argon2/algorithm/Finalize.java b/src/main/java/at/gadermaier/argon2/algorithm/Finalize.java index 93911af..c9156c5 100644 --- a/src/main/java/at/gadermaier/argon2/algorithm/Finalize.java +++ b/src/main/java/at/gadermaier/argon2/algorithm/Finalize.java @@ -8,12 +8,12 @@ public class Finalize { public static void finalize(Instance instance, Argon2 argon2) { - Block finalBlock = instance.memory[instance.getLaneLength() - 1]; + Block finalBlock = instance.memory(instance.getLaneLength() - 1); /* XOR the last blocks */ for (int i = 1; i < instance.getLanes(); i++) { int lastBlockInLane = i * instance.getLaneLength() + (instance.getLaneLength() - 1); - finalBlock.xorWith(instance.memory[lastBlockInLane]); + finalBlock.xorWith(instance.memory(lastBlockInLane)); } byte[] finalBlockBytes = finalBlock.toBytes(); diff --git a/src/main/java/at/gadermaier/argon2/algorithm/Functions.java b/src/main/java/at/gadermaier/argon2/algorithm/Functions.java index acf3792..113aa60 100644 --- a/src/main/java/at/gadermaier/argon2/algorithm/Functions.java +++ b/src/main/java/at/gadermaier/argon2/algorithm/Functions.java @@ -1,11 +1,9 @@ package at.gadermaier.argon2.algorithm; -import at.gadermaier.argon2.Util; -import at.gadermaier.argon2.blake2.Blake2b; -import at.gadermaier.argon2.model.Block; - import static at.gadermaier.argon2.Constants.*; +import at.gadermaier.argon2.blake2.Blake2b; + class Functions { @@ -13,13 +11,13 @@ class Functions { * H0 = H64(p, τ, m, t, v, y, |P|, P, |S|, S, |L|, K, |X|, X) * -> 64 byte (ARGON2_PREHASH_DIGEST_LENGTH) */ - static byte[] initialHash(byte[] lanes, byte[] outputLength, - byte[] memory, byte[] iterations, - byte[] version, byte[] type, - byte[] passwordLength, byte[] password, - byte[] saltLength, byte[] salt, - byte[] secretLength, byte[] secret, - byte[] additionalLength, byte[] additional) { + static byte[] initialHash(int lanes, int outputLength, + int memory, int iterations, + int version, int type, + int passwordLength, byte[] password, + int saltLength, byte[] salt, + int secretLength, byte[] secret, + int additionalLength, byte[] additional) { Blake2b.Param params = new Blake2b.Param() @@ -69,17 +67,15 @@ static byte[] blake2bLong(byte[] input, int outputLength) { assert (input.length == ARGON2_PREHASH_SEED_LENGTH || input.length == ARGON2_BLOCK_SIZE); byte[] result = new byte[outputLength]; - byte[] outlenBytes = Util.intToLittleEndianBytes(outputLength); - int blake2bLength = 64; if (outputLength <= blake2bLength) { - result = blake2b(input, outlenBytes, outputLength); + result = blake2b(input, true, outputLength, outputLength); } else { byte[] outBuffer; /* V1 */ - outBuffer = blake2b(input, outlenBytes, blake2bLength); + outBuffer = blake2b(input, true, outputLength, blake2bLength); System.arraycopy(outBuffer, 0, result, 0, blake2bLength / 2); int r = (outputLength / 32) + (outputLength % 32 == 0 ? 0 : 1) - 2; @@ -87,14 +83,14 @@ static byte[] blake2bLong(byte[] input, int outputLength) { int position = blake2bLength / 2; for (int i = 2; i <= r; i++, position += blake2bLength / 2) { /* V2 to Vr */ - outBuffer = blake2b(outBuffer, null, blake2bLength); + outBuffer = blake2b(outBuffer, false, 0, blake2bLength); System.arraycopy(outBuffer, 0, result, position, blake2bLength / 2); } int lastLength = outputLength - 32 * r; /* Vr+1 */ - outBuffer = blake2b(outBuffer, null, lastLength); + outBuffer = blake2b(outBuffer, false, 0, lastLength); System.arraycopy(outBuffer, 0, result, position, lastLength); } @@ -102,64 +98,18 @@ static byte[] blake2bLong(byte[] input, int outputLength) { return result; } - private static byte[] blake2b(byte[] input, byte[] outlenBytes, int outputLength) { + private static byte[] blake2b(byte[] input, boolean withLen, int outlen, int outputLength) { Blake2b.Param params = new Blake2b.Param() .setDigestLength(outputLength); final Blake2b blake2b = Blake2b.Digest.newInstance(params); - if (outlenBytes != null) - blake2b.update(outlenBytes); + if (withLen) { + blake2b.update(outlen); + } blake2b.update(input); return blake2b.digest(); } - - static void roundFunction(Block block, - int v0, int v1, int v2, int v3, - int v4, int v5, int v6, int v7, - int v8, int v9, int v10, int v11, - int v12, int v13, int v14, int v15) { - - F(block, v0, v4, v8, v12); - F(block, v1, v5, v9, v13); - F(block, v2, v6, v10, v14); - F(block, v3, v7, v11, v15); - - F(block, v0, v5, v10, v15); - F(block, v1, v6, v11, v12); - F(block, v2, v7, v8, v13); - F(block, v3, v4, v9, v14); - } - - private static void F(Block block, int a, int b, int c, int d) { - fBlaMka(block, a, b); - rotr64(block, d, a, 32); - - fBlaMka(block, c, d); - rotr64(block, b, c, 24); - - fBlaMka(block, a, b); - rotr64(block, d, a, 16); - - fBlaMka(block, c, d); - rotr64(block, b, c, 63); - } - - /*designed by the Lyra PHC team */ - /* a <- a + b + 2*aL*bL - * + == addition modulo 2^64 - * aL = least 32 bit */ - private static void fBlaMka(Block block, int x, int y) { - final long m = 0xFFFFFFFFL; - final long xy = (block.v[x] & m) * (block.v[y] & m); - - block.v[x] = block.v[x] + block.v[y] + 2 * xy; - } - - private static void rotr64(Block block, int v, int w, long c) { - final long temp = block.v[v] ^ block.v[w]; - block.v[v] = (temp >>> c) | (temp << (64 - c)); - } } diff --git a/src/main/java/at/gadermaier/argon2/algorithm/Initialize.java b/src/main/java/at/gadermaier/argon2/algorithm/Initialize.java index cdd7a0a..dd51b5f 100644 --- a/src/main/java/at/gadermaier/argon2/algorithm/Initialize.java +++ b/src/main/java/at/gadermaier/argon2/algorithm/Initialize.java @@ -1,29 +1,29 @@ package at.gadermaier.argon2.algorithm; +import static at.gadermaier.argon2.Constants.*; + import at.gadermaier.argon2.Argon2; import at.gadermaier.argon2.Util; import at.gadermaier.argon2.model.Instance; -import static at.gadermaier.argon2.Constants.*; - public class Initialize { public static void initialize(Instance instance, Argon2 argon2) { byte[] initialHash = Functions.initialHash( - Util.intToLittleEndianBytes(argon2.getLanes()), - Util.intToLittleEndianBytes(argon2.getOutputLength()), - Util.intToLittleEndianBytes(argon2.getMemory()), - Util.intToLittleEndianBytes(argon2.getIterations()), - Util.intToLittleEndianBytes(argon2.getVersion()), - Util.intToLittleEndianBytes(argon2.getType().ordinal()), - Util.intToLittleEndianBytes(argon2.getPasswordLength()), + argon2.getLanes(), + argon2.getOutputLength(), + argon2.getMemory(), + argon2.getIterations(), + argon2.getVersion(), + argon2.getType().ordinal(), + argon2.getPasswordLength(), argon2.getPassword(), - Util.intToLittleEndianBytes(argon2.getSaltLength()), + argon2.getSaltLength(), argon2.getSalt(), - Util.intToLittleEndianBytes(argon2.getSecretLength()), + argon2.getSecretLength(), argon2.getSecret(), - Util.intToLittleEndianBytes(argon2.getAdditionalLength()), + argon2.getAdditionalLength(), argon2.getAdditional() ); fillFirstBlocks(instance, initialHash); @@ -42,17 +42,14 @@ private static void fillFirstBlocks(Instance instance, byte[] initialHash) { byte[] initialHashWithOnes = getInitialHashLong(initialHash, oneBytes); for (int i = 0; i < instance.getLanes(); i++) { - - byte[] iBytes = Util.intToLittleEndianBytes(i); - - System.arraycopy(iBytes, 0, initialHashWithZeros, ARGON2_PREHASH_DIGEST_LENGTH + 4, 4); - System.arraycopy(iBytes, 0, initialHashWithOnes, ARGON2_PREHASH_DIGEST_LENGTH + 4, 4); + Util.writeInt(initialHashWithZeros, ARGON2_PREHASH_DIGEST_LENGTH + 4, i); + Util.writeInt(initialHashWithOnes, ARGON2_PREHASH_DIGEST_LENGTH + 4, i); byte[] blockhashBytes = Functions.blake2bLong(initialHashWithZeros, ARGON2_BLOCK_SIZE); - instance.memory[i * instance.getLaneLength() + 0].fromBytes(blockhashBytes); + instance.memory(i * instance.getLaneLength() + 0).fromBytes(blockhashBytes); blockhashBytes = Functions.blake2bLong(initialHashWithOnes, ARGON2_BLOCK_SIZE); - instance.memory[i * instance.getLaneLength() + 1].fromBytes(blockhashBytes); + instance.memory(i * instance.getLaneLength() + 1).fromBytes(blockhashBytes); } } diff --git a/src/main/java/at/gadermaier/argon2/blake2/Blake2b.java b/src/main/java/at/gadermaier/argon2/blake2/Blake2b.java index 4710923..76a1646 100644 --- a/src/main/java/at/gadermaier/argon2/blake2/Blake2b.java +++ b/src/main/java/at/gadermaier/argon2/blake2/Blake2b.java @@ -27,13 +27,15 @@ To the extent possible under law, the author(s) have dedicated all copyright package at.gadermaier.argon2.blake2; +import static at.gadermaier.argon2.blake2.Blake2b.Engine.Assert.*; +import static at.gadermaier.argon2.blake2.Blake2b.Engine.LittleEndian.*; + import java.io.PrintStream; import java.security.Key; import java.security.spec.AlgorithmParameterSpec; import java.util.Arrays; -import static at.gadermaier.argon2.blake2.Blake2b.Engine.Assert.*; -import static at.gadermaier.argon2.blake2.Blake2b.Engine.LittleEndian.*; +import at.gadermaier.argon2.Util; /** */ @@ -49,6 +51,9 @@ public interface Blake2b { /** */ void update(byte input); + /** */ + void update(int input); + /** */ void update(byte[] input, int offset, int len); @@ -314,8 +319,6 @@ static class Engine implements Blake2b { * compressor cache buffer offset/cached data length */ private int buflen; - /** to support update(byte) */ - private byte[] oneByte; /** Basic use constructor pending (TODO) JCA/JCE compliance */ Engine() { @@ -331,7 +334,6 @@ static class Engine implements Blake2b { assert param != null : "param is null"; this.param = param; this.buffer = new byte[Spec.block_bytes]; - this.oneByte = new byte[1]; this.outlen = param.getDigestLength(); if (param.getDepth() > Param.Default.depth) { @@ -415,12 +417,7 @@ final public void reset() { len -= Spec.block_bytes; off += Spec.block_bytes; } - } else if ( buflen == Spec.block_bytes ) { - /* flush */ - this.t[0] += Spec.block_bytes; - this.t[1] += this.t[0] == 0 ? 1 : 0; - compress( buffer, 0 ); - buflen = 0; + } else if (flush()) { continue; } @@ -436,12 +433,37 @@ final public void reset() { } } + /** + * Flushes the {@link #buffer}, if it is full. + * + * @return Whether a flush happened (and the buffer is empty after the operation). + */ + private boolean flush() { + boolean full = buflen == Spec.block_bytes; + if ( full ) { + /* flush */ + this.t[0] += Spec.block_bytes; + this.t[1] += this.t[0] == 0 ? 1 : 0; + compress( buffer, 0 ); + buflen = 0; + } + return full; + } + /** {@inheritDoc} */ @Override final public void update (byte b) { - oneByte[0] = b; - update (oneByte, 0, 1); + flush(); + buffer[buflen++] = b; } + /** {@inheritDoc} */ + @Override final public void update (int bytes) { + update(Util.byte0(bytes)); + update(Util.byte1(bytes)); + update(Util.byte2(bytes)); + update(Util.byte3(bytes)); + } + /** {@inheritDoc} */ @Override final public void update(byte[] input) { update (input, 0, input.length); diff --git a/src/main/java/at/gadermaier/argon2/model/Block.java b/src/main/java/at/gadermaier/argon2/model/Block.java index b83b508..4259ad5 100644 --- a/src/main/java/at/gadermaier/argon2/model/Block.java +++ b/src/main/java/at/gadermaier/argon2/model/Block.java @@ -1,61 +1,76 @@ package at.gadermaier.argon2.model; -import at.gadermaier.argon2.Util; +import static at.gadermaier.argon2.Constants.*; import java.util.Arrays; -import static at.gadermaier.argon2.Constants.ARGON2_BLOCK_SIZE; -import static at.gadermaier.argon2.Constants.ARGON2_QWORDS_IN_BLOCK; +import at.gadermaier.argon2.Util; + +public final class Block { -public class Block { + private static final long MASK = 0xFFFFFFFFL; - /* 128 * 8 Byte QWords */ + private static final int SIZE = ARGON2_QWORDS_IN_BLOCK; + + /* 128 * 8 Byte QWords */ public long[] v; public Block() { - v = new long[ARGON2_QWORDS_IN_BLOCK]; + v = new long[SIZE]; } public void fromBytes(byte[] input) { assert (input.length == ARGON2_BLOCK_SIZE); - for (int i = 0; i < v.length; i++) { - byte[] slice = Arrays.copyOfRange(input, i * 8, (i + 1) * 8); - v[i] = Util.littleEndianBytesToLong(slice); + int offset = 0; + long[] v0 = v; + for (int i = 0; i < SIZE; i++) { + v0[i] = Util.readLong(input, offset); + offset += 8; } } public byte[] toBytes() { byte[] result = new byte[ARGON2_BLOCK_SIZE]; - for (int i = 0; i < v.length; i++) { - byte[] bytes = Util.longToLittleEndianBytes(v[i]); - System.arraycopy(bytes, 0, result, i * bytes.length, bytes.length); + int offset = 0; + long[] v0 = v; + for (int i = 0; i < SIZE; i++) { + offset = Util.writeLong(result, offset, v0[i]); } return result; } - public void copyBlock(Block other) { - System.arraycopy(other.v, 0, v, 0, v.length); + public void copyFrom(Block other) { + System.arraycopy(other.v, 0, v, 0, SIZE); } public void xor(Block b1, Block b2) { - for (int i = 0; i < v.length; i++) { - v[i] = b1.v[i] ^ b2.v[i]; + long[] v0 = v; + long[] v1 = b1.v; + long[] v2 = b2.v; + for (int i = 0; i < SIZE; i++) { + v0[i] = v1[i] ^ v2[i]; } } public void xor(Block b1, Block b2, Block b3) { - for (int i = 0; i < v.length; i++) { - v[i] = b1.v[i] ^ b2.v[i] ^ b3.v[i]; + long[] v0 = v; + long[] v1 = b1.v; + long[] v2 = b2.v; + long[] v3 = b3.v; + for (int i = 0; i < SIZE; i++) { + v0[i] = v1[i] ^ v2[i] ^ v3[i]; } } - public void xorWith(Block other) { - for (int i = 0; i < v.length; i++) { - v[i] = v[i] ^ other.v[i]; + public void xorWith(Block b1) { + long[] v0 = v; + long[] v1 = b1.v; + for (int i = 0; i < SIZE; i++) { + v0[i] ^= v1[i]; } } @@ -69,8 +84,52 @@ public String toString() { return result.toString(); } - void clear() { + public void clear() { Arrays.fill(v, 0); } + + public final void roundFunction(int v0, int v1, int v2, int v3, int v4, int v5, + int v6, int v7, int v8, int v9, int v10, int v11, int v12, int v13, + int v14, int v15) { + long[] vec = v; + F(vec, v0, v4, v8, v12); + F(vec, v1, v5, v9, v13); + F(vec, v2, v6, v10, v14); + F(vec, v3, v7, v11, v15); + + F(vec, v0, v5, v10, v15); + F(vec, v1, v6, v11, v12); + F(vec, v2, v7, v8, v13); + F(vec, v3, v4, v9, v14); + } + + private static void F(long[] v0, int a, int b, int c, int d) { + fBlaMka(v0, a, b); + rotr64(v0, d, a, 32); + + fBlaMka(v0, c, d); + rotr64(v0, b, c, 24); + + fBlaMka(v0, a, b); + rotr64(v0, d, a, 16); + + fBlaMka(v0, c, d); + rotr64(v0, b, c, 63); + } + + /*designed by the Lyra PHC team */ + /* a <- a + b + 2*aL*bL + * + == addition modulo 2^64 + * aL = least 32 bit */ + private static void fBlaMka(long[] v0, int x, int y) { + final long xy = (v0[x] & MASK) * (v0[y] & MASK); + v0[x] += v0[y] + 2 * xy; + } + + private static void rotr64(long[] v0, int a, int b, int c) { + final long xab = v0[a] ^ v0[b]; + v0[a] = (xab >>> c) | (xab << (64 - c)); + } + } diff --git a/src/main/java/at/gadermaier/argon2/model/Instance.java b/src/main/java/at/gadermaier/argon2/model/Instance.java index 2c30e33..e9f3c74 100644 --- a/src/main/java/at/gadermaier/argon2/model/Instance.java +++ b/src/main/java/at/gadermaier/argon2/model/Instance.java @@ -1,12 +1,12 @@ package at.gadermaier.argon2.model; -import at.gadermaier.argon2.Argon2; +import static at.gadermaier.argon2.Constants.*; -import static at.gadermaier.argon2.Constants.ARGON2_SYNC_POINTS; +import at.gadermaier.argon2.Argon2; public class Instance { - public Block[] memory; + private Block[] memory; private int version; private int iterations; private int segmentLength; @@ -53,6 +53,14 @@ public void clear() { memory = null; } + public final int memoryLength() { + return memory.length; + } + + public final Block memory(int index) { + return memory[index]; + } + public Block[] getMemory() { return memory; } diff --git a/src/test/java/Argon2Test.java b/src/test/java/Argon2Test.java index b480e16..42d852f 100644 --- a/src/test/java/Argon2Test.java +++ b/src/test/java/Argon2Test.java @@ -1,127 +1,205 @@ -import at.gadermaier.argon2.Argon2Factory; -import org.junit.Test; +import at.gadermaier.argon2.Argon2; +import at.gadermaier.argon2.Argon2Result; +import at.gadermaier.argon2.model.Argon2Type; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import static at.gadermaier.argon2.Constants.ARGON2_VERSION_10; -import static at.gadermaier.argon2.Constants.Defaults.ARGON2_VERSION_NUMBER; -import static at.gadermaier.argon2.Constants.Defaults.OUTLEN_DEF; -import static org.junit.Assert.fail; +import static at.gadermaier.argon2.Constants.ARGON2_VERSION_13; +import static at.gadermaier.argon2.model.Argon2Type.Argon2i; +import static at.gadermaier.argon2.model.Argon2Type.Argon2id; +import static com.google.common.truth.Truth.assertThat; -public class Argon2Test { + +class Argon2Test { @Test - public void basicTest(){ - - boolean largeRam = false; - - int version = ARGON2_VERSION_10; - System.out.println("Test Argon2i version number: " + version); - /* Multiple test cases for various input values */ - hashtest(version, 2, 16, 1, "password", "somesalt", - "f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694", - "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ" + - "$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ", OUTLEN_DEF); - if(largeRam){ - - hashtest(version, 2, 20, 1, "password", "somesalt", - "9690ec55d28d3ed32562f2e73ea62b02b018757643a2ae6e79528459de8106e9", - "$argon2i$m=1048576,t=2,p=1$c29tZXNhbHQ" + - "$lpDsVdKNPtMlYvLnPqYrArAYdXZDoq5ueVKEWd6BBuk", OUTLEN_DEF); - - hashtest(version, 2, 18, 1, "password", "somesalt", - "3e689aaa3d28a77cf2bc72a51ac53166761751182f1ee292e3f677a7da4c2467", - "$argon2i$m=262144,t=2,p=1$c29tZXNhbHQ" + - "$Pmiaqj0op3zyvHKlGsUxZnYXURgvHuKS4/Z3p9pMJGc", OUTLEN_DEF); - } - hashtest(version, 2, 8, 1, "password", "somesalt", - "fd4dd83d762c49bdeaf57c47bdcd0c2f1babf863fdeb490df63ede9975fccf06", - "$argon2i$m=256,t=2,p=1$c29tZXNhbHQ" + - "$/U3YPXYsSb3q9XxHvc0MLxur+GP960kN9j7emXX8zwY", OUTLEN_DEF); - hashtest(version, 2, 8, 2, "password", "somesalt", - "b6c11560a6a9d61eac706b79a2f97d68b4463aa3ad87e00c07e2b01e90c564fb", - "$argon2i$m=256,t=2,p=2$c29tZXNhbHQ" + - "$tsEVYKap1h6scGt5ovl9aLRGOqOth+AMB+KwHpDFZPs", OUTLEN_DEF); - hashtest(version, 1, 16, 1, "password", "somesalt", - "81630552b8f3b1f48cdb1992c4c678643d490b2b5eb4ff6c4b3438b5621724b2", - "$argon2i$m=65536,t=1,p=1$c29tZXNhbHQ" + - "$gWMFUrjzsfSM2xmSxMZ4ZD1JCytetP9sSzQ4tWIXJLI", OUTLEN_DEF); - hashtest(version, 4, 16, 1, "password", "somesalt", - "f212f01615e6eb5d74734dc3ef40ade2d51d052468d8c69440a3a1f2c1c2847b", - "$argon2i$m=65536,t=4,p=1$c29tZXNhbHQ" + - "$8hLwFhXm6110c03D70Ct4tUdBSRo2MaUQKOh8sHChHs", OUTLEN_DEF); - hashtest(version, 2, 16, 1, "differentpassword", "somesalt", - "e9c902074b6754531a3a0be519e5baf404b30ce69b3f01ac3bf21229960109a3", - "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ" + - "$6ckCB0tnVFMaOgvlGeW69ASzDOabPwGsO/ISKZYBCaM", OUTLEN_DEF); - hashtest(version, 2, 16, 1, "password", "diffsalt", - "79a103b90fe8aef8570cb31fc8b22259778916f8336b7bdac3892569d4f1c497", - "$argon2i$m=65536,t=2,p=1$ZGlmZnNhbHQ" + - "$eaEDuQ/orvhXDLMfyLIiWXeJFvgza3vaw4kladTxxJc", OUTLEN_DEF); - - hashtest(version, 2, 16, 1, "password", "diffsalt", - "1a097a5d1c80e579583f6e19c7e4763ccb7c522ca85b7d58143738e12ca39f8e6e42734c950ff2463675b97c37ba39feba4a9cd9cc5b4c798f2aaf70eb4bd044c8d148decb569870dbd923430b82a083f284beae777812cce18cdac68ee8ccefc6ec9789f30a6b5a034591f51af830f4", - "$argon2i$m=65536,t=2,p=1$ZGlmZnNhbHQ" + - "$eaEDuQ/orvhXDLMfyLIiWXeJFvgza3vaw4kladTxxJc", 112); - - - version = ARGON2_VERSION_NUMBER; - System.out.println("Test Argon2i version number: " + version); - - /* Multiple test cases for various input values */ - hashtest(version, 2, 16, 1, "password", "somesalt", - "c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0", - "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" + - "$wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA", OUTLEN_DEF); - if(largeRam) { - hashtest(version, 2, 20, 1, "password", "somesalt", - "d1587aca0922c3b5d6a83edab31bee3c4ebaef342ed6127a55d19b2351ad1f41", - "$argon2i$v=19$m=1048576,t=2,p=1$c29tZXNhbHQ" + - "$0Vh6ygkiw7XWqD7asxvuPE667zQu1hJ6VdGbI1GtH0E", OUTLEN_DEF); - - hashtest(version, 2, 18, 1, "password", "somesalt", - "296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb", - "$argon2i$v=19$m=262144,t=2,p=1$c29tZXNhbHQ" + - "$KW266AuAfNzqrUSudBtQbxTbCVkmexg7EY+bJCKbx8s", OUTLEN_DEF); - } - hashtest(version, 2, 8, 1, "password", "somesalt", - "89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f", - "$argon2i$v=19$m=256,t=2,p=1$c29tZXNhbHQ" + - "$iekCn0Y3spW+sCcFanM2xBT63UP2sghkUoHLIUpWRS8", OUTLEN_DEF); - hashtest(version, 2, 8, 2, "password", "somesalt", - "4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61", - "$argon2i$v=19$m=256,t=2,p=2$c29tZXNhbHQ" + - "$T/XOJ2mh1/TIpJHfCdQan76Q5esCFVoT5MAeIM1Oq2E", OUTLEN_DEF); - hashtest(version, 1, 16, 1, "password", "somesalt", - "d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf", - "$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQ" + - "$0WgHXE2YXhPr6uVgz4uUw7XYoWxRkWtvSsLaOsEbvs8", OUTLEN_DEF); - hashtest(version, 4, 16, 1, "password", "somesalt", - "aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b", - "$argon2i$v=19$m=65536,t=4,p=1$c29tZXNhbHQ" + - "$qqlT1YrzcGzj3xrv1KZKhOMdf1QXUjHxKFJZ+IF0zls", OUTLEN_DEF); - hashtest(version, 2, 16, 1, "differentpassword", "somesalt", - "14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee", - "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" + - "$FK6NoBr+qHAMI1jc73xTWNkCEoK9iGY6RWL1n7dNIu4", OUTLEN_DEF); - hashtest(version, 2, 16, 1, "password", "diffsalt", - "b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271", - "$argon2i$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ" + - "$sDV8zPvvkfOGCw26RHsjSMvv7K2vmQq/6cxAcmxSEnE", OUTLEN_DEF); + void testI10() { + testHash( Argon2i, ARGON2_VERSION_10, 2, 16, 1, 32, "password", "somesalt", + "f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694", + "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ" + + "$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" + ); + testHash( Argon2i, ARGON2_VERSION_10, 2, 18, 1, 32, "password", "somesalt", + "3e689aaa3d28a77cf2bc72a51ac53166761751182f1ee292e3f677a7da4c2467", + "$argon2i$m=262144,t=2,p=1$c29tZXNhbHQ" + + "$Pmiaqj0op3zyvHKlGsUxZnYXURgvHuKS4/Z3p9pMJGc" + ); + testHash( Argon2i, ARGON2_VERSION_10, 2, 8, 1, 32, "password", "somesalt", + "fd4dd83d762c49bdeaf57c47bdcd0c2f1babf863fdeb490df63ede9975fccf06", + "$argon2i$m=256,t=2,p=1$c29tZXNhbHQ" + + "$/U3YPXYsSb3q9XxHvc0MLxur+GP960kN9j7emXX8zwY" + ); + testHash( Argon2i, ARGON2_VERSION_10, 2, 8, 2, 32, "password", "somesalt", + "b6c11560a6a9d61eac706b79a2f97d68b4463aa3ad87e00c07e2b01e90c564fb", + "$argon2i$m=256,t=2,p=2$c29tZXNhbHQ" + + "$tsEVYKap1h6scGt5ovl9aLRGOqOth+AMB+KwHpDFZPs" + ); + testHash( Argon2i, ARGON2_VERSION_10, 1, 16, 1, 32, "password", "somesalt", + "81630552b8f3b1f48cdb1992c4c678643d490b2b5eb4ff6c4b3438b5621724b2", + "$argon2i$m=65536,t=1,p=1$c29tZXNhbHQ" + + "$gWMFUrjzsfSM2xmSxMZ4ZD1JCytetP9sSzQ4tWIXJLI" + ); + testHash( Argon2i, ARGON2_VERSION_10, 4, 16, 1, 32, "password", "somesalt", + "f212f01615e6eb5d74734dc3ef40ade2d51d052468d8c69440a3a1f2c1c2847b", + "$argon2i$m=65536,t=4,p=1$c29tZXNhbHQ" + + "$8hLwFhXm6110c03D70Ct4tUdBSRo2MaUQKOh8sHChHs" + ); + testHash( Argon2i, ARGON2_VERSION_10, 2, 16, 1, 32, "differentpassword", "somesalt", + "e9c902074b6754531a3a0be519e5baf404b30ce69b3f01ac3bf21229960109a3", + "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ" + + "$6ckCB0tnVFMaOgvlGeW69ASzDOabPwGsO/ISKZYBCaM" + ); + testHash( Argon2i, ARGON2_VERSION_10, 2, 16, 1, 32, "password", "diffsalt", + "79a103b90fe8aef8570cb31fc8b22259778916f8336b7bdac3892569d4f1c497", + "$argon2i$m=65536,t=2,p=1$ZGlmZnNhbHQ" + + "$eaEDuQ/orvhXDLMfyLIiWXeJFvgza3vaw4kladTxxJc" + ); } - private void hashtest(int version, int iterations, int memory, int parallelism, - String password, String salt, String passwordRef, String mcfref, int outputLength) { + @Test + void testI13() { + testHash( Argon2i, ARGON2_VERSION_13, 2, 16, 1, 32, "password", "somesalt", + "c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0", + "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" + + "$wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA" + ); + testHash( Argon2i, ARGON2_VERSION_13, 2, 18, 1, 32, "password", "somesalt", + "296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb", + "$argon2i$v=19$m=262144,t=2,p=1$c29tZXNhbHQ" + + "$KW266AuAfNzqrUSudBtQbxTbCVkmexg7EY+bJCKbx8s" + ); + testHash( Argon2i, ARGON2_VERSION_13, 2, 8, 1, 32, "password", "somesalt", + "89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f", + "$argon2i$v=19$m=256,t=2,p=1$c29tZXNhbHQ" + + "$iekCn0Y3spW+sCcFanM2xBT63UP2sghkUoHLIUpWRS8" + ); + testHash( Argon2i, ARGON2_VERSION_13, 2, 8, 2, 32, "password", "somesalt", + "4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61", + "$argon2i$v=19$m=256,t=2,p=2$c29tZXNhbHQ" + + "$T/XOJ2mh1/TIpJHfCdQan76Q5esCFVoT5MAeIM1Oq2E" + ); + testHash( Argon2i, ARGON2_VERSION_13, 1, 16, 1, 32, "password", "somesalt", + "d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf", + "$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQ" + + "$0WgHXE2YXhPr6uVgz4uUw7XYoWxRkWtvSsLaOsEbvs8" + ); + testHash( Argon2i, ARGON2_VERSION_13, 4, 16, 1, 32, "password", "somesalt", + "aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b", + "$argon2i$v=19$m=65536,t=4,p=1$c29tZXNhbHQ" + + "$qqlT1YrzcGzj3xrv1KZKhOMdf1QXUjHxKFJZ+IF0zls" + ); + testHash( Argon2i, ARGON2_VERSION_13, 2, 16, 1, 32, "differentpassword", "somesalt", + "14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee", + "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" + + "$FK6NoBr+qHAMI1jc73xTWNkCEoK9iGY6RWL1n7dNIu4" + ); + testHash( Argon2i, ARGON2_VERSION_13, 2, 16, 1, 32, "password", "diffsalt", + "b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271", + "$argon2i$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ" + + "$sDV8zPvvkfOGCw26RHsjSMvv7K2vmQq/6cxAcmxSEnE" + ); + } + + @Test + void testID13() { + + testHash( Argon2id, ARGON2_VERSION_13, 2, 16, 1, 32, "password", "somesalt", + "09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7", + "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" + + "$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc" + ); + testHash( Argon2id, ARGON2_VERSION_13, 2, 18, 1, 32, "password", "somesalt", + "78fe1ec91fb3aa5657d72e710854e4c3d9b9198c742f9616c2f085bed95b2e8c", + "$argon2id$v=19$m=262144,t=2,p=1$c29tZXNhbHQ" + + "$eP4eyR+zqlZX1y5xCFTkw9m5GYx0L5YWwvCFvtlbLow" + ); + testHash( Argon2id, ARGON2_VERSION_13, 2, 8, 1, 32, "password", "somesalt", + "9dfeb910e80bad0311fee20f9c0e2b12c17987b4cac90c2ef54d5b3021c68bfe", + "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" + + "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" + ); + testHash( Argon2id, ARGON2_VERSION_13, 2, 8, 2, 32, "password", "somesalt", + "6d093c501fd5999645e0ea3bf620d7b8be7fd2db59c20d9fff9539da2bf57037", + "$argon2id$v=19$m=256,t=2,p=2$c29tZXNhbHQ" + + "$bQk8UB/VmZZF4Oo79iDXuL5/0ttZwg2f/5U52iv1cDc" + ); + testHash( Argon2id, ARGON2_VERSION_13, 1, 16, 1, 32, "password", "somesalt", + "f6a5adc1ba723dddef9b5ac1d464e180fcd9dffc9d1cbf76cca2fed795d9ca98", + "$argon2id$v=19$m=65536,t=1,p=1$c29tZXNhbHQ" + + "$9qWtwbpyPd3vm1rB1GThgPzZ3/ydHL92zKL+15XZypg" + ); + testHash( Argon2id, ARGON2_VERSION_13, 4, 16, 1, 32, "password", "somesalt", + "9025d48e68ef7395cca9079da4c4ec3affb3c8911fe4f86d1a2520856f63172c", + "$argon2id$v=19$m=65536,t=4,p=1$c29tZXNhbHQ" + + "$kCXUjmjvc5XMqQedpMTsOv+zyJEf5PhtGiUghW9jFyw" + ); + testHash( Argon2id, ARGON2_VERSION_13, 2, 16, 1, 32, "differentpassword", "somesalt", + "0b84d652cf6b0c4beaef0dfe278ba6a80df6696281d7e0d2891b817d8c458fde", + "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" + + "$C4TWUs9rDEvq7w3+J4umqA32aWKB1+DSiRuBfYxFj94" + ); + testHash( Argon2id, ARGON2_VERSION_13, 2, 16, 1, 32, "password", "diffsalt", + "bdf32b05ccc42eb15d58fd19b1f856b113da1e9a5874fdcc544308565aa8141c", + "$argon2id$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ" + + "$vfMrBczELrFdWP0ZsfhWsRPaHppYdP3MVEMIVlqoFBw" + ); + } + + @EnabledIfEnvironmentVariable( named = "LARGE_RAM", matches = "true" ) + @Test + void testLargeRam() { + + testHash( Argon2i, ARGON2_VERSION_10, 2, 20, 1, 32, "password", "somesalt", + "9690ec55d28d3ed32562f2e73ea62b02b018757643a2ae6e79528459de8106e9", + "$argon2i$m=1048576,t=2,p=1$c29tZXNhbHQ" + + "$lpDsVdKNPtMlYvLnPqYrArAYdXZDoq5ueVKEWd6BBuk" + ); + testHash( Argon2i, ARGON2_VERSION_13, 2, 20, 1, 32, "password", "somesalt", + "d1587aca0922c3b5d6a83edab31bee3c4ebaef342ed6127a55d19b2351ad1f41", + "$argon2i$v=19$m=1048576,t=2,p=1$c29tZXNhbHQ" + + "$0Vh6ygkiw7XWqD7asxvuPE667zQu1hJ6VdGbI1GtH0E" + ); + } + private Argon2Result test( + Argon2Type type, int version, int iterations, int memory, int parallelism, int outputLength, + String password, String salt ) { + + return Argon2.create() + .setType( type ) + .setVersion( version ) + .setIterations( iterations ) + .setMemory( memory ) + .setParallelism( parallelism ) + .setOutputLength( outputLength ) + .hash( password, salt ); + } + + private void testHash( + Argon2Type type, int version, int iterations, int memory, int parallelism, int outputLength, + String password, String salt, String passwordRef, String mcfref ) { + + assertResult( + test( type, version, iterations, memory, parallelism, outputLength, password, salt ), + password, passwordRef, mcfref + ); + } + + private void assertResult( Argon2Result result, String password, String passwordRef, String encoded ) { + assertThat( result.asString() ).isEqualTo( passwordRef ); + assertThat( Argon2.checkHash( encoded, password ) ).isTrue(); + } + + @Test + void testEncoded() { + String encoded = test( Argon2i, 19, 3, 12, 1, 32, "password", "somesalt" ).asEncoded(); + assertThat( encoded ).isEqualTo( "$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$iWh06vD8Fy27wf9npn6FXWiCX4K6pW6Ue1Bnzz07Z8A" ); - String result = Argon2Factory.create() - .setVersion(version) - .setIterations(iterations) - .setMemory(memory) - .setParallelism(parallelism) - .setOutputLength(outputLength) - .hash(password.toCharArray(), salt); + encoded = test( Argon2id, 19, 3, 12, 1, 32, "password", "somesalt" ).asEncoded(); + assertThat( encoded ).isEqualTo( "$argon2id$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$qLml5cbqFAO6YxVHhrSBHP0UWdxrIxkNcM8aMX3blzU" ); - if (!result.equals(passwordRef)) { - fail(); - } + encoded = test( Argon2id, 16, 4, 16, 4, 32, "password", "somesalt" ).asEncoded(); + assertThat( encoded ).isEqualTo( "$argon2id$v=16$m=65536,t=4,p=4$c29tZXNhbHQ$ZjGrB2S6NxRX2/v18DtFDrfZ3EhCImmES6nAppGsU2w" ); } } diff --git a/src/test/java/BaseTest.java b/src/test/java/BaseTest.java deleted file mode 100644 index 8ab77c9..0000000 --- a/src/test/java/BaseTest.java +++ /dev/null @@ -1,15 +0,0 @@ -import at.gadermaier.argon2.Argon2; -import at.gadermaier.argon2.Argon2Factory; -import org.junit.Before; - -public abstract class BaseTest { - - protected Argon2 argon2; - - @Before - public void setUp(){ - argon2 = Argon2Factory.create() - .setPassword("password".toCharArray()) - .setSalt("saltsalt"); - } -} diff --git a/src/test/java/InitializeTest.java b/src/test/java/InitializeTest.java index 68792e8..3cd6980 100644 --- a/src/test/java/InitializeTest.java +++ b/src/test/java/InitializeTest.java @@ -1,12 +1,26 @@ +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.charset.Charset; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import at.gadermaier.argon2.Argon2; import at.gadermaier.argon2.algorithm.Initialize; import at.gadermaier.argon2.model.Instance; -import org.junit.Test; -import static junit.framework.TestCase.assertEquals; -public class InitializeTest extends BaseTest { +public class InitializeTest { private String instanceNull = "0011a4db4b4dc5422bfab9973caf41bd78d9b20f9a01c236e329c65ffa67c2e6d655eada0eddbc82fe29d0a8fc33ca267e76889ac563e5fcf2a61cb395bd32404a9f9f6bed4324328e4671614efd85ebaa3ff319a4b5996c3fb04e85188839580c945aa12081d3496c6f9a25bf3214d0f766e54c372361a0f3d62ad7acc5cb0306b8fca890e0fcdf932597e3f819ff1ae836976dae0af12b8d53036d1cff4b3bbf5a25de57828ed2de7baa364f0b32ff3ce0626c7cb1e3b76d9bf9a7857e051c1c7a3368792fa1a01a79a7b6c83f97a2cfb3c40c9a7c5f3616673f1f97716a10c6e27a950fb737aab6c544ab59bff3f328a13acf432942c0096420ac7a1ed2de34c181aaf71802ed9a56184476b983e011a50a6203f628d9028d1b442c006d95cbe819acc754451862eab50d7df1ec6ac6bcd55169a7c64f00a3305609df52a178498c44e1d11adf56661e600eeeaf5132864fb99e937cfd3173141e3e619451f1b774719fe6c9d16a73edad3fd494e7520cdefe9e77e210ae2bff00d398e91d6a387775e0a6d9806dc88fa4c481bf4968565cd82df21d135d3bba1cb64c6b65a99d56dd55143f6658d99a5e2c6768394a1036e9cf32fdbaaf704a28d6671423ceb5b54074eb0bde342d541b597a27bf6eced39c168f47da2650df61f96280c1c0f3b858d51534061180c7185ff5f0293a76be3a165ad547303c81c7a480e9eaeddf7f5770b90c3600535b1b3f4be981c357fe08f8db0405ff7d4bc7e5fa94653038aa72ea2f4d4bbb6eb7e5ac76166c57151f0f9c1d26e0307e0d037119e647d33399e8bd0a66b60d18bd0e53af50658e8288b3e829365743abb199c5c59cc910756eb6eacd2a1d73646e47215998d96458be9b00ebf04fc46cdd651d02339822400b5a9565ba11e4b368fbcbdc6ee047ef6e93e66fc08aa0b24349f929d6d3122f2b96b683d709a9c18f584914df5397cf106076fe63823c63470b4c641a7057cf6a690956b6c03db91133db7620cb5e929204a0771c17ac3edb4778e95cb41173c91db45740e1023493242b684cefa0544e384adc8913c4e7bd4711088c2b87d1b922c59bed69a5afbc241cc270268eab791969e3d4181199295af1562631cc5147f8e17607620d965509623ff65261319f2084fb670e473fa990f2b52a6a3fe9dd9b4630e124d7c1b21df4fe4db64b53d4e8c1be5f50093d271a2a7818518ecb786f3c5d36555d348eb3f26fe7031d491167f1c8cd1974e75eb3a205949b3f8b4be0d7dffdee49069f6fa23bd8dec2d950aad3bacae2d3208d543171868665f9d1d6837dab20672d25a32b6794cfc1492a8cf06dcc142a9ca91dc81f00d20038fd7e26e0af42e2a4727a3b4f7b49a17cf3f660635a18063773e3999c3bda22e749b4cb7da41c455ac3e261d64e992d58e28f8157687c5ace8d26349544b3"; + private Argon2 argon2; + + @BeforeEach + public void setUp(){ + argon2 = Argon2.create() + .setPassword("password".getBytes( Charset.forName( "UTF-8" ) )) + .setSalt("saltsalt".getBytes( Charset.forName( "UTF-8" ) )); + } @Test public void basicTest(){ @@ -14,6 +28,6 @@ public void basicTest(){ Instance instance = new Instance(argon2); Initialize.initialize(instance, argon2); - assertEquals(instanceNull, instance.memory[0].toString()); + assertEquals(instanceNull, instance.memory(0).toString()); } } diff --git a/src/test/java/MainSuite.java b/src/test/java/MainSuite.java deleted file mode 100644 index 432a015..0000000 --- a/src/test/java/MainSuite.java +++ /dev/null @@ -1,11 +0,0 @@ -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - -@RunWith(Suite.class) -@SuiteClasses({ - Argon2Test.class, - InitializeTest.class -}) -public class MainSuite { -} diff --git a/src/test/java/TestAgainstReferenceImpl.java b/src/test/java/TestAgainstReferenceImpl.java new file mode 100644 index 0000000..e2abde9 --- /dev/null +++ b/src/test/java/TestAgainstReferenceImpl.java @@ -0,0 +1,119 @@ +import static at.gadermaier.argon2.Constants.Defaults.*; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Random; + +import org.junit.Assert; +import org.junit.Test; + +import at.gadermaier.argon2.Argon2; +import at.gadermaier.argon2.model.Argon2Type; + +/** + * Tests this Argon2 implementation (pure in java) against the implementation "argon2-jvm" in version 2.5. + */ +public class TestAgainstReferenceImpl { + + private static Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + @Test + public void testSameHashes() { + System.out.println("VM processors: " + Runtime.getRuntime().availableProcessors()); + for (int i = 1; i < 35; i++) { + System.out.println("Iteration " + i); + Random random = new Random(i); + int iterations = 5 + random.nextInt(11); + int memory = 65536 + random.nextInt(65537); + int parallelism = 1 + random.nextInt(6); + char[] password = new char[1 + random.nextInt(32)]; + for (int j = 0; j < password.length; j++) { + password[j] = (char) (32 + random.nextInt(96)); + } + byte[] salt = new byte[16 + random.nextInt(32)]; + random.nextBytes(salt); + assertEquals(iterations, memory, parallelism, password, salt); + } + } + + private void assertEquals(int iterations, int memory, int parallelism, char[] password, byte[] salt) { + assertEquals(Argon2Type.Argon2id, de.mkammerer.argon2.Argon2Factory.Argon2Types.ARGON2id, iterations, memory, parallelism, password, salt); + assertEquals(Argon2Type.Argon2i, de.mkammerer.argon2.Argon2Factory.Argon2Types.ARGON2i, iterations, memory, parallelism, password, salt); + assertEquals(Argon2Type.Argon2d, de.mkammerer.argon2.Argon2Factory.Argon2Types.ARGON2d, iterations, memory, parallelism, password, salt); + } + + private void assertEquals(Argon2Type type, de.mkammerer.argon2.Argon2Factory.Argon2Types referenceType, int iterations, int memory, int parallelism, + char[] password, byte[] salt) { + int version = 19; + Argon2 argon2 = Argon2.create().setVersion(version).setType(type).setOutputLength(OUTLEN_DEF); + de.mkammerer.argon2.Argon2Advanced referenceArgon2 = de.mkammerer.argon2.Argon2Factory.createAdvanced(referenceType); + + System.gc(); + long timeBaseReference = System.currentTimeMillis(); + long memoryBaseReference = Runtime.getRuntime().freeMemory(); + String referenceHash = referenceHash(referenceArgon2, iterations, memory, parallelism, password, salt); + long memoryReference = memoryBaseReference - Runtime.getRuntime().freeMemory(); + long timeReference = System.currentTimeMillis(); + + System.gc(); + long timeBaseArgon2 = System.currentTimeMillis(); + long memoryBaseArgon2 = Runtime.getRuntime().freeMemory(); + String hash = hash(argon2, iterations, memory, parallelism, password, Arrays.copyOf(salt, salt.length)); + long memoryArgon2 = memoryBaseArgon2 - Runtime.getRuntime().freeMemory(); + long timeArgon2 = System.currentTimeMillis(); + logTime(timeReference - timeBaseReference, timeArgon2 - timeBaseArgon2); + System.out.println("Memory usage: reference: " + memoryReference + ", argon2: " + memoryArgon2 + ", overhead: " + (((((float)memoryArgon2) / memoryReference) - 1) * 100) + "%"); + Assert.assertEquals("Hash differs.", referenceHash, hash); + } + + private void logTime(long timeReferenceHashComputation, long timeHashComputation) { + StringBuilder msg = new StringBuilder(); + msg.append("CPU usage: reference: "); + msg.append(toTime(timeReferenceHashComputation)); + msg.append(", argon2: "); + msg.append(toTime(timeHashComputation)); + msg.append(", overhead: "); + msg.append(((((float)timeHashComputation) / timeReferenceHashComputation) -1) * 100); + msg.append("%"); + System.out.println(msg.toString()); + } + + private String hash(Argon2 argon2, int iterations, int memory, int parallelism, char[] password, byte[] salt) { + return argon2.setIterations(iterations) + .setMemoryInKiB(memory) + .setParallelism(parallelism) + .hash(toByteArray(password), salt) + .asEncoded(); + } + + private String referenceHash(de.mkammerer.argon2.Argon2Advanced argon2, int iterations, int memory, int parallelism, char[] password, byte[] salt) { + return argon2.hash(iterations, memory, parallelism, password, DEFAULT_CHARSET, salt); + } + + private byte[] toByteArray(char[] passwd) { + return new String(passwd).getBytes(DEFAULT_CHARSET); + } + + private String toTime(long duration) { + StringBuilder res = new StringBuilder(); + if (duration < 0) { + duration = -duration; + res.append("-"); + } + long s = duration / 1000; + res.append(s); + res.append("s "); + long ms = duration - (s * 1000); + res.append(ms); + res.append("ms"); + return res.toString(); + } + + /** + * Runs the benchmark from the command line. + */ + public static void main(String[] args) { + new TestAgainstReferenceImpl().testSameHashes(); + } + +}