From d5f060c7e9ac0acd2bf30ce9ef6123c9cb471f29 Mon Sep 17 00:00:00 2001 From: Steffen Heil Date: Thu, 4 Jan 2018 07:45:09 +0000 Subject: [PATCH] Added own implementation --- java/RocaDetect.java | 60 ++++++ java/RocaDetectFull.java | 434 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 494 insertions(+) create mode 100644 java/RocaDetect.java create mode 100644 java/RocaDetectFull.java diff --git a/java/RocaDetect.java b/java/RocaDetect.java new file mode 100644 index 0000000..2613c1d --- /dev/null +++ b/java/RocaDetect.java @@ -0,0 +1,60 @@ + + +import java.math.BigInteger; +import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; + + +public class RocaDetect +{ + + private static final BigInteger primorial = new BigInteger( "962947420735983927056946215901134429196419130606213075415963491270" ); + + private static final BigInteger generatorOrder = new BigInteger( "2454106387091158800" ); + + private static final BigInteger[][] precomputed = { // + { new BigInteger( "16" ), new BigInteger( "153381649193197425" ), new BigInteger( "579701604149392295790310832658859347575600566378196268451889670917" ) }, // + { new BigInteger( "81" ), new BigInteger( "30297609717174800" ), new BigInteger( "277722225047912451353908303068039414108454325703031162502745467431" ) }, // + { new BigInteger( "25" ), new BigInteger( "98164255483646352" ), new BigInteger( "234201887969172179017831590126685452395189736370272535972311924721" ) }, // + { new BigInteger( "7" ), new BigInteger( "350586626727308400" ), new BigInteger( "465795348279120036271926818215675896518889071711237747055072416431" ) }, // + { new BigInteger( "11" ), new BigInteger( "223100580644650800" ), new BigInteger( "437012106259973544013288076253697869776983569163405597124590041661" ) }, // + { new BigInteger( "13" ), new BigInteger( "188777414391627600" ), new BigInteger( "600233314679500130765997367568070397728989341087574577462488273851" ) }, // + { new BigInteger( "17" ), new BigInteger( "144359199240656400" ), new BigInteger( "490583871282757313557677439310697711820073497975201318061467049731" ) }, // + { new BigInteger( "23" ), new BigInteger( "106700277699615600" ), new BigInteger( "898534743120550745345039665105359785761728305552651906893573158241" ) }, // + { new BigInteger( "29" ), new BigInteger( "84624358175557200" ), new BigInteger( "48963428173016131884251502503447513348970464268112529258438821591" ) }, // + { new BigInteger( "37" ), new BigInteger( "66327199651112400" ), new BigInteger( "652736171102915279414440052389359579522404914035084030986659816231" ) }, // + { new BigInteger( "41" ), new BigInteger( "59856253343686800" ), new BigInteger( "452469269984377989821938583375231840224823446911353131821958748911" ) }, // + { new BigInteger( "53" ), new BigInteger( "46303894096059600" ), new BigInteger( "341981326990349432038915478544328115041718943579776606222491707181" ) }, // + { new BigInteger( "83" ), new BigInteger( "29567546832423600" ), new BigInteger( "651575200857282537469670193992983176641888393763485494143735775531" ) } // + }; + + + public static final boolean isVulnerable( PublicKey publicKey ) + { + if ( ! ( publicKey instanceof RSAPublicKey ) ) + return false; + return isVulnerable( ( (RSAPublicKey) publicKey ).getModulus() ); + } + + + public static final boolean isVulnerable( BigInteger modulus ) + { + if ( ! modulus.modPow( generatorOrder, primorial ).equals( BigInteger.ONE ) ) + return false; + outer: for ( BigInteger[] array : precomputed ) { + BigInteger primeToPower = array[ 0 ]; // factorPower.factor.pow( factorPower.power ) + BigInteger orderDivPrimePower = array[ 1 ]; // generatorOrder.divide( primeToPower ); // g.div(generator_order, prime_to_power) + BigInteger generatorDash = array[ 2 ]; // generator.modPow( orderDivPrimePower, primorial ); + BigInteger modulusDash = modulus.modPow( orderDivPrimePower, primorial ); + if ( modulusDash.equals( BigInteger.ONE ) ) + continue outer; + BigInteger generatorDashToI = generatorDash; + for ( int i = 1, max = primeToPower.intValueExact(); i < max; i ++ , generatorDashToI = generatorDashToI.multiply( generatorDash ).mod( primorial ) ) + if ( generatorDashToI.equals( modulusDash ) ) + continue outer; + return false; + } + return true; + } + +} diff --git a/java/RocaDetectFull.java b/java/RocaDetectFull.java new file mode 100644 index 0000000..814d724 --- /dev/null +++ b/java/RocaDetectFull.java @@ -0,0 +1,434 @@ + +import java.math.BigInteger; +import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.List; + + +/** + * https://crocs.fi.muni.cz/public/papers/rsa_ccs17 + * + * This generator inner class contains the translation of the original algorithm. + * https://github.com/crocs-muni/roca/blob/master/roca/detect.py + * commit 2f51c855adc1d7f541a546baeccbb70c8db2838d + * + * The original implementation included its own mulInv implementation, but we use BigInteger.modInverse. + * The implementation of mulInv is included as is prime3 which was not used in the first place. + * + * However to detect vulnerable keys the actual result of discreteLog was not required, just if it is null or not. + * The vulnerability test in the parent class therefore skips the computation of the result and just replies if the key is vulnerable. + * To speed up computation even more three values computed in the original implementation of discreteLog are now precomputed. + * + * The total performance advantage is about 65%, but the more important improvement is the reduction of code, + * the reduced number of needed constants and the quicker initialization. + * If the original algorithm is modified parameter-wise the new constants can be generated by running the main method. + * + * The original moduli fingerprint algorithm is also provided but is deprecated due to the higher false positive rate. + **/ +public class RocaDetectFull +{ + + private static final BigInteger primorial = new BigInteger( "962947420735983927056946215901134429196419130606213075415963491270" ); + + private static final BigInteger generatorOrder = new BigInteger( "2454106387091158800" ); + + private static final BigInteger[][] precomputed = { // + { new BigInteger( "16" ), new BigInteger( "153381649193197425" ), new BigInteger( "579701604149392295790310832658859347575600566378196268451889670917" ) }, // + { new BigInteger( "81" ), new BigInteger( "30297609717174800" ), new BigInteger( "277722225047912451353908303068039414108454325703031162502745467431" ) }, // + { new BigInteger( "25" ), new BigInteger( "98164255483646352" ), new BigInteger( "234201887969172179017831590126685452395189736370272535972311924721" ) }, // + { new BigInteger( "7" ), new BigInteger( "350586626727308400" ), new BigInteger( "465795348279120036271926818215675896518889071711237747055072416431" ) }, // + { new BigInteger( "11" ), new BigInteger( "223100580644650800" ), new BigInteger( "437012106259973544013288076253697869776983569163405597124590041661" ) }, // + { new BigInteger( "13" ), new BigInteger( "188777414391627600" ), new BigInteger( "600233314679500130765997367568070397728989341087574577462488273851" ) }, // + { new BigInteger( "17" ), new BigInteger( "144359199240656400" ), new BigInteger( "490583871282757313557677439310697711820073497975201318061467049731" ) }, // + { new BigInteger( "23" ), new BigInteger( "106700277699615600" ), new BigInteger( "898534743120550745345039665105359785761728305552651906893573158241" ) }, // + { new BigInteger( "29" ), new BigInteger( "84624358175557200" ), new BigInteger( "48963428173016131884251502503447513348970464268112529258438821591" ) }, // + { new BigInteger( "37" ), new BigInteger( "66327199651112400" ), new BigInteger( "652736171102915279414440052389359579522404914035084030986659816231" ) }, // + { new BigInteger( "41" ), new BigInteger( "59856253343686800" ), new BigInteger( "452469269984377989821938583375231840224823446911353131821958748911" ) }, // + { new BigInteger( "53" ), new BigInteger( "46303894096059600" ), new BigInteger( "341981326990349432038915478544328115041718943579776606222491707181" ) }, // + { new BigInteger( "83" ), new BigInteger( "29567546832423600" ), new BigInteger( "651575200857282537469670193992983176641888393763485494143735775531" ) } // + }; + + + public static final boolean isVulnerable( PublicKey publicKey ) + { + if ( ! ( publicKey instanceof RSAPublicKey ) ) + return false; + return isVulnerable( ( (RSAPublicKey) publicKey ).getModulus() ); + } + + + public static final boolean isVulnerable( BigInteger modulus ) + { + if ( ! modulus.modPow( generatorOrder, primorial ).equals( BigInteger.ONE ) ) + return false; + outer: for ( BigInteger[] array : precomputed ) { + BigInteger primeToPower = array[ 0 ]; // factorPower.factor.pow( factorPower.power ) + BigInteger orderDivPrimePower = array[ 1 ]; // generatorOrder.divide( primeToPower ); // g.div(generator_order, prime_to_power) + BigInteger generatorDash = array[ 2 ]; // generator.modPow( orderDivPrimePower, primorial ); + BigInteger modulusDash = modulus.modPow( orderDivPrimePower, primorial ); + if ( modulusDash.equals( BigInteger.ONE ) ) + continue outer; + BigInteger generatorDashToI = generatorDash; + for ( int i = 1, max = primeToPower.intValueExact(); i < max; i ++ , generatorDashToI = generatorDashToI.multiply( generatorDash ).mod( primorial ) ) + if ( generatorDashToI.equals( modulusDash ) ) + continue outer; + return false; + } + return true; + } + + + @SuppressWarnings( "hiding" ) + public static class Generator + { + + static class Factor + { + + final BigInteger factor; + + final int power; + + + public Factor( BigInteger factor, int power ) + { + this.factor = factor; + this.power = power; + } + + } + + + private static final BigInteger[] primes = { // + BigInteger.valueOf( 3 ), // + BigInteger.valueOf( 5 ), // + BigInteger.valueOf( 7 ), // + BigInteger.valueOf( 11 ), // + BigInteger.valueOf( 13 ), // + BigInteger.valueOf( 17 ), // + BigInteger.valueOf( 19 ), // + BigInteger.valueOf( 23 ), // + BigInteger.valueOf( 29 ), // + BigInteger.valueOf( 31 ), // + BigInteger.valueOf( 37 ), // + BigInteger.valueOf( 41 ), // + BigInteger.valueOf( 43 ), // + BigInteger.valueOf( 47 ), // + BigInteger.valueOf( 53 ), // + BigInteger.valueOf( 59 ), // + BigInteger.valueOf( 61 ), // + BigInteger.valueOf( 67 ), // + BigInteger.valueOf( 71 ), // + BigInteger.valueOf( 73 ), // + BigInteger.valueOf( 79 ), // + BigInteger.valueOf( 83 ), // + BigInteger.valueOf( 89 ), // + BigInteger.valueOf( 97 ), // + BigInteger.valueOf( 101 ), // + BigInteger.valueOf( 103 ), // + BigInteger.valueOf( 107 ), // + BigInteger.valueOf( 109 ), // + BigInteger.valueOf( 113 ), // + BigInteger.valueOf( 127 ), // + BigInteger.valueOf( 131 ), // + BigInteger.valueOf( 137 ), // + BigInteger.valueOf( 139 ), // + BigInteger.valueOf( 149 ), // + BigInteger.valueOf( 151 ), // + BigInteger.valueOf( 157 ), // + BigInteger.valueOf( 163 ), // + BigInteger.valueOf( 167 ) // + }; + + private static final BigInteger[] prints = { // + new BigInteger( "6" ), // + new BigInteger( "30" ), // + new BigInteger( "126" ), // + new BigInteger( "1026" ), // + new BigInteger( "5658" ), // + new BigInteger( "107286" ), // + new BigInteger( "199410" ), // + new BigInteger( "8388606" ), // + new BigInteger( "536870910" ), // + new BigInteger( "2147483646" ), // + new BigInteger( "67109890" ), // + new BigInteger( "2199023255550" ), // + new BigInteger( "8796093022206" ), // + new BigInteger( "140737488355326" ), // + new BigInteger( "5310023542746834" ), // + new BigInteger( "576460752303423486" ), // + new BigInteger( "1455791217086302986" ), // + new BigInteger( "147573952589676412926" ), // + new BigInteger( "20052041432995567486" ), // + new BigInteger( "6041388139249378920330" ), // + new BigInteger( "207530445072488465666" ), // + new BigInteger( "9671406556917033397649406" ), // + new BigInteger( "618970019642690137449562110" ), // + new BigInteger( "79228162521181866724264247298" ), // + new BigInteger( "2535301200456458802993406410750" ), // + new BigInteger( "1760368345969468176824550810518" ), // + new BigInteger( "50079290986288516948354744811034" ), // + new BigInteger( "473022961816146413042658758988474" ), // + new BigInteger( "10384593717069655257060992658440190" ), // + new BigInteger( "144390480366845522447407333004847678774" ), // + new BigInteger( "2722258935367507707706996859454145691646" ), // + new BigInteger( "174224571863520493293247799005065324265470" ), // + new BigInteger( "696898287454081973172991196020261297061886" ), // + new BigInteger( "713623846352979940529142984724747568191373310" ), // + new BigInteger( "1800793591454480341970779146165214289059119882" ), // + new BigInteger( "126304807362733370595828809000324029340048915994" ), // + new BigInteger( "11692013098647223345629478661730264157247460343806" ), // + new BigInteger( "187072209578355573530071658587684226515959365500926" ), // + }; + + + public final static boolean isVulnerableModuli( PublicKey publicKey ) + { + if ( ! ( publicKey instanceof RSAPublicKey ) ) + return false; + return isVulnerableModuli( ( (RSAPublicKey) publicKey ).getModulus() ); + } + + + public final static boolean isVulnerableModuli( BigInteger modulus ) + { + for ( int i = 0; i < primes.length; i ++ ) + if ( ! prints[ i ].testBit( modulus.mod( primes[ i ] ).intValue() ) ) + return false; + return true; + } + + + private static final BigInteger ONE = BigInteger.ONE; + + private static final BigInteger TWO = BigInteger.valueOf( 2 ); + + private static final BigInteger THREE = BigInteger.valueOf( 3 ); + + private static final BigInteger FOUR = BigInteger.valueOf( 4 ); + + private static final BigInteger maxPrime = primes[ primes.length - 1 ]; + + private static final BigInteger primorial; + + private static final BigInteger phiPrimorial; + + static { + BigInteger _primorial = TWO; + BigInteger _phiPrimorial = ONE; + for ( BigInteger prime : primes ) { + _primorial = _primorial.multiply( prime ); + _phiPrimorial = _phiPrimorial.multiply( prime.subtract( ONE ) ); + } + primorial = _primorial; + phiPrimorial = _phiPrimorial; + } + + private static final Factor[] phiPrimorialDecomposition = primeFactors( phiPrimorial ); + + private static final BigInteger generator = new BigInteger( "65537" ); + + private static final BigInteger generatorOrder = order( generator ); + + private static final Factor[] generatorOrderDecomposition = primeFactors( generatorOrder ); + + + @SuppressWarnings( "unused" ) + private static final boolean prime3( BigInteger value ) + { + if ( value.bitLength() < 5 ) { + int intValue = value.intValue(); + if ( intValue < 2 ) + return false; + if ( intValue == 2 || intValue == 3 ) + return true; + } + if ( value.testBit( 0 ) || value.mod( THREE ).signum() == 0 ) + return false; + BigInteger max = sqrtCeil( value ); + BigInteger d = BigInteger.valueOf( 5 ); + boolean nextIs2 = true; + while ( d.compareTo( max ) < 0 ) { + if ( value.mod( d ).signum() == 0 ) + return false; + d = d.add( nextIs2 ? TWO : FOUR ); + nextIs2 = ! nextIs2; + } + return true; + } + + + private static final Factor[] primeFactors( BigInteger value ) + { + List result = new ArrayList<>(); + int count = 0; + while ( ! value.testBit( 0 ) ) { + if ( count ++ < 0 ) + throw new Error( "overflow" ); + value = value.shiftRight( 1 ); + } + if ( count > 0 ) { + result.add( new Factor( TWO, count ) ); + count = 0; + } + BigInteger[] divResult; + while ( ( divResult = value.divideAndRemainder( THREE ) )[ 1 ].signum() == 0 ) { + if ( count ++ < 0 ) + throw new Error( "overflow" ); + value = divResult[ 0 ]; + } + if ( count > 0 ) { + result.add( new Factor( THREE, count ) ); + count = 0; + } + BigInteger max = sqrtCeil( value ); + if ( max.compareTo( maxPrime ) > 0 ) + max = maxPrime; + BigInteger factor = BigInteger.valueOf( 5 ); + boolean nextIsTwo = true; + while ( factor.compareTo( max ) < 0 ) { + while ( ( divResult = value.divideAndRemainder( factor ) )[ 1 ].signum() == 0 ) { + if ( count ++ < 0 ) + throw new Error( "overflow" ); + value = divResult[ 0 ]; + } + if ( count > 0 ) { + result.add( new Factor( factor, count ) ); + count = 0; + } + factor = factor.add( nextIsTwo ? TWO : FOUR ); + nextIsTwo = ! nextIsTwo; + } + if ( value.compareTo( TWO ) > 1 ) + result.add( new Factor( value, 1 ) ); + return result.toArray( new Factor[ result.size() ] ); + } + + + private static final BigInteger order( BigInteger element ) + { + if ( element.equals( ONE ) ) + return ONE; + if ( ! element.modPow( phiPrimorial, primorial ).equals( ONE ) ) + return null; + BigInteger order = phiPrimorial; + for ( Factor factorPower : phiPrimorialDecomposition ) + for ( int power = 1; power <= factorPower.power; power ++ ) { + BigInteger nextOrder = order.divide( factorPower.factor ); + if ( ! element.modPow( nextOrder, primorial ).equals( ONE ) ) + break; + order = nextOrder; + } + return order; + } + + + private static final BigInteger chineseRemainder( BigInteger[] n, BigInteger[] a ) + { + BigInteger sum = BigInteger.ZERO; + BigInteger prod = ONE; + for ( BigInteger one : n ) + prod = prod.multiply( one ); + int count = Math.min( n.length, a.length ); + for ( int i = 0; i < count; i ++ ) { + BigInteger p = prod.divide( n[ i ] ); + sum = sum.add( a[ i ].multiply( /* mulInv */ p.modInverse( n[ i ] ) ).multiply( p ) ); + } + return sum.mod( prod ); + } + + + /** Using BigInteger.modInverse instead **/ + @SuppressWarnings( "unused" ) + private static final BigInteger mulInv( BigInteger a, BigInteger b ) + { + if ( b.equals( ONE ) ) + return ONE; + BigInteger a0 = a, b0 = b; + BigInteger x0 = BigInteger.ZERO, x1 = ONE, temp; + while ( a.compareTo( ONE ) > 0 ) { + BigInteger q[] = a.divideAndRemainder( b ); + a = b; + b = q[ 1 ]; + temp = x0; + x0 = x1.subtract( q[ 0 ].multiply( x0 ) ); + x1 = temp; + } + return x1.signum() < 0 ? x1.add( b0 ) : x1; + } + + + private static final BigInteger discreteLog( BigInteger element, BigInteger generator, BigInteger generatorOrder, Factor[] generatorOrderDecomposition, BigInteger modulus ) + { + if ( ! element.modPow( generatorOrder, modulus ).equals( ONE ) ) + return null; + List moduli = new ArrayList<>(); + List remainders = new ArrayList<>(); + for ( Factor one : generatorOrderDecomposition ) { + BigInteger primeToPower = one.factor.pow( one.power ); + BigInteger orderDivPrimePower = generatorOrder.divide( primeToPower ); // g.div(generator_order, prime_to_power) + BigInteger gDash = generator.modPow( orderDivPrimePower, modulus ); + BigInteger hDash = element.modPow( orderDivPrimePower, modulus ); + boolean found = false; + for ( BigInteger i = BigInteger.ZERO; i.compareTo( primeToPower ) < 0; i = i.add( ONE ) ) + if ( gDash.modPow( i, modulus ).equals( hDash ) ) { + remainders.add( i ); + moduli.add( primeToPower ); + found = true; + break; + } + if ( ! found ) + return null; + } + return chineseRemainder( moduli.toArray( new BigInteger[ moduli.size() ] ), remainders.toArray( new BigInteger[ remainders.size() ] ) ); + } + + + public static final boolean isVulnerableDlog( PublicKey publicKey ) + { + if ( ! ( publicKey instanceof RSAPublicKey ) ) + return false; + return isVulnerableDlog( ( (RSAPublicKey) publicKey ).getModulus() ); + } + + + public static final boolean isVulnerableDlog( BigInteger modulus ) + { + return discreteLog( modulus, generator, generatorOrder, generatorOrderDecomposition, primorial ) != null; + } + + + private static BigInteger sqrtCeil( BigInteger x ) + throws IllegalArgumentException + { + BigInteger y, t; + for ( y = x.shiftRight( 1 ); y.compareTo( t = x.divide( y ) ) > 0; ) + y = ( t.add( y ) ).shiftRight( 1 ); + return x.compareTo( y.multiply( y ) ) == 0 ? y : y.add( ONE ); + } + + + private static String encode( Factor factorPower ) + { + BigInteger primeToPower = factorPower.factor.pow( factorPower.power ); + BigInteger orderDivPrimePower = generatorOrder.divide( primeToPower ); // g.div(generator_order, prime_to_power) + BigInteger generatorDash = generator.modPow( orderDivPrimePower, primorial ); + return "{ new BigInteger( \"" + primeToPower + "\" ), new BigInteger( \"" + orderDivPrimePower + "\" ), new BigInteger( \"" + generatorDash + "\" ) }"; + } + + + public static void main( String[] args ) + { + System.out.println( "private static final BigInteger primorial = new BigInteger( \"" + primorial + "\" );" ); + System.out.println( "private static final BigInteger generatorOrder = new BigInteger( \"" + generatorOrder + "\" );" ); + System.out.println( "private static final BigInteger[][] precomputed = { //" ); + for ( int i = 0; i < generatorOrderDecomposition.length - 1; i ++ ) + System.out.println( encode( generatorOrderDecomposition[ i ] ) + ", //" ); + System.out.println( encode( generatorOrderDecomposition[ generatorOrderDecomposition.length - 1 ] ) + " //" ); + System.out.println( "};" ); + } + + } + +}