116f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giropackage org.bouncycastle.crypto.generators; 216f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 353b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giroimport java.math.BigInteger; 453b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro 516f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giroimport org.bouncycastle.crypto.AsymmetricCipherKeyPair; 616f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giroimport org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; 716f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giroimport org.bouncycastle.crypto.KeyGenerationParameters; 816f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giroimport org.bouncycastle.crypto.params.RSAKeyGenerationParameters; 916f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giroimport org.bouncycastle.crypto.params.RSAKeyParameters; 1016f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giroimport org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; 11bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giroimport org.bouncycastle.math.Primes; 1253b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giroimport org.bouncycastle.math.ec.WNafUtil; 1316f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 1416f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro/** 1516f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro * an RSA key pair generator. 1616f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro */ 1716f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giropublic class RSAKeyPairGenerator 1816f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro implements AsymmetricCipherKeyPairGenerator 1916f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro{ 2016f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro private static final BigInteger ONE = BigInteger.valueOf(1); 2116f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 2216f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro private RSAKeyGenerationParameters param; 23bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro private int iterations; 2416f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 2553b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro public void init(KeyGenerationParameters param) 2616f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro { 2716f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro this.param = (RSAKeyGenerationParameters)param; 28bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro this.iterations = getNumberOfIterations(this.param.getStrength(), this.param.getCertainty()); 2916f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro } 3016f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 3116f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro public AsymmetricCipherKeyPair generateKeyPair() 3216f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro { 3353b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro AsymmetricCipherKeyPair result = null; 3453b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro boolean done = false; 3516f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 36bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro // 37bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro // p and q values should have a length of half the strength in bits 38bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro // 39bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro int strength = param.getStrength(); 40bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro int pbitlength = (strength + 1) / 2; 41bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro int qbitlength = strength - pbitlength; 42bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro int mindiffbits = (strength / 2) - 100; 43bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro 44bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro if (mindiffbits < strength / 3) 4553b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro { 46bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro mindiffbits = strength / 3; 47bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro } 4816f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 49bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro int minWeight = strength >> 2; 5016f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 51bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro // d lower bound is 2^(strength / 2) 52bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro BigInteger dLowerBound = BigInteger.valueOf(2).pow(strength / 2); 53bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro // squared bound (sqrt(2)*2^(nlen/2-1))^2 54bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro BigInteger squaredBound = ONE.shiftLeft(strength - 1); 55bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro // 2^(nlen/2 - 100) 56bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro BigInteger minDiff = ONE.shiftLeft(mindiffbits); 5716f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 58bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro while (!done) 59bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro { 60bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro BigInteger p, q, n, d, e, pSub1, qSub1, gcd, lcm; 6116f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 62bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro e = param.getPublicExponent(); 63bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro 64bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro p = chooseRandomPrime(pbitlength, e, squaredBound); 6553b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro 6653b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // 6753b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // generate a modulus of the required length 6816f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro // 69bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro for (; ; ) 7016f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro { 71bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro q = chooseRandomPrime(qbitlength, e, squaredBound); 7216f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 7353b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // p and q should not be too close together (or equal!) 7453b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro BigInteger diff = q.subtract(p).abs(); 75bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro if (diff.bitLength() < mindiffbits || diff.compareTo(minDiff) <= 0) 7616f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro { 7716f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro continue; 7816f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro } 7953b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro 8053b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // 8153b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // calculate the modulus 8253b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // 8353b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro n = p.multiply(q); 8453b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro 8553b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro if (n.bitLength() != strength) 8616f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro { 8753b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // 8853b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // if we get here our primes aren't big enough, make the largest 8953b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // of the two p and try again 9053b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // 9153b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro p = p.max(q); 9216f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro continue; 9316f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro } 9453b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro 9553b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro /* 96bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro * Require a minimum weight of the NAF representation, since low-weight composites may 9753b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro * be weak against a version of the number-field-sieve for factoring. 9853b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro * 9953b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro * See "The number field sieve for integers of low weight", Oliver Schirokauer. 10053b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro */ 10153b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro if (WNafUtil.getNafWeight(n) < minWeight) 10216f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro { 103bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro p = chooseRandomPrime(pbitlength, e, squaredBound); 10416f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro continue; 10516f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro } 10653b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro 10753b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro break; 10853b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro } 10953b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro 11053b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro if (p.compareTo(q) < 0) 11153b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro { 112bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro gcd = p; 11353b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro p = q; 114bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro q = gcd; 11516f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro } 11616f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 11753b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro pSub1 = p.subtract(ONE); 11853b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro qSub1 = q.subtract(ONE); 119bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro gcd = pSub1.gcd(qSub1); 120bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro lcm = pSub1.divide(gcd).multiply(qSub1); 12153b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro 12216f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro // 12353b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // calculate the private exponent 12416f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro // 12553b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro d = e.modInverse(lcm); 12616f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 127bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro if (d.compareTo(dLowerBound) <= 0) 12816f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro { 12953b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro continue; 13053b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro } 13153b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro else 13253b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro { 13353b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro done = true; 13453b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro } 13516f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 13616f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro // 13753b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro // calculate the CRT factors 13816f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro // 13953b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro BigInteger dP, dQ, qInv; 14016f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 14153b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro dP = d.remainder(pSub1); 14253b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro dQ = d.remainder(qSub1); 14353b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro qInv = q.modInverse(p); 14416f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 14553b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro result = new AsymmetricCipherKeyPair( 14653b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro new RSAKeyParameters(false, n, e), 14753b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro new RSAPrivateCrtKeyParameters(n, e, d, p, q, dP, dQ, qInv)); 14853b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro } 14916f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 15053b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro return result; 15153b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro } 15216f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 15353b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro /** 15453b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro * Choose a random prime value for use with RSA 155bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro * 15653b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro * @param bitlength the bit-length of the returned prime 157bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro * @param e the RSA public exponent 158bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro * @return A prime p, with (p-1) relatively prime to e 15953b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro */ 160bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro protected BigInteger chooseRandomPrime(int bitlength, BigInteger e, BigInteger sqrdBound) 16153b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro { 162bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro for (int i = 0; i != 5 * bitlength; i++) 16353b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro { 16453b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro BigInteger p = new BigInteger(bitlength, 1, param.getRandom()); 165bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro 16653b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro if (p.mod(e).equals(ONE)) 16753b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro { 16853b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro continue; 16953b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro } 170bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro 171bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro if (p.multiply(p).compareTo(sqrdBound) < 0) 172bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro { 173bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro continue; 174bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro } 175bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro 176bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro if (!isProbablePrime(p)) 17753b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro { 17853b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro continue; 17953b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro } 18016f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro 181bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro if (!e.gcd(p.subtract(ONE)).equals(ONE)) 18253b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro { 18353b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro continue; 18453b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro } 185bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro 18653b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro return p; 18753b61f9fe9d58034fcc7021137e92460f91b70ceSergio Giro } 188bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro 189bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro throw new IllegalStateException("unable to generate prime number for RSA key"); 190bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro } 191bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro 192bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro protected boolean isProbablePrime(BigInteger x) 193bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro { 194bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro /* 195bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro * Primes class for FIPS 186-4 C.3 primality checking 196bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro */ 197bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro return !Primes.hasAnySmallFactors(x) && Primes.isMRProbablePrime(x, param.getRandom(), iterations); 198bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro } 199bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro 200bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro private static int getNumberOfIterations(int bits, int certainty) 201bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro { 202bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro /* 203bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro * NOTE: We enforce a minimum 'certainty' of 100 for bits >= 1024 (else 80). Where the 204bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro * certainty is higher than the FIPS 186-4 tables (C.2/C.3) cater to, extra iterations 205bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro * are added at the "worst case rate" for the excess. 206bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro */ 207bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro if (bits >= 1536) 208bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro { 209bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro return certainty <= 100 ? 3 210bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro : certainty <= 128 ? 4 211bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro : 4 + (certainty - 128 + 1) / 2; 212bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro } 213bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro else if (bits >= 1024) 214bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro { 215bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro return certainty <= 100 ? 4 216bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro : certainty <= 112 ? 5 217bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro : 5 + (certainty - 112 + 1) / 2; 218bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro } 219bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro else if (bits >= 512) 220bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro { 221bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro return certainty <= 80 ? 5 222bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro : certainty <= 100 ? 7 223bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro : 7 + (certainty - 100 + 1) / 2; 224bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro } 225bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro else 226bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro { 227bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro return certainty <= 80 ? 40 228bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro : 40 + (certainty - 80 + 1) / 2; 229bdb7b3d37025690a0434040b4e0d0623d9fa74afSergio Giro } 23016f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro } 23116f9ee464b68937f45d009d9c1b0eb9b544a8deeSergio Giro} 232