CryptoService.java

  1. /*
  2.  * Copyright (c) 2002-2022, City of Paris
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  *
  9.  *  1. Redistributions of source code must retain the above copyright notice
  10.  *     and the following disclaimer.
  11.  *
  12.  *  2. Redistributions in binary form must reproduce the above copyright notice
  13.  *     and the following disclaimer in the documentation and/or other materials
  14.  *     provided with the distribution.
  15.  *
  16.  *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
  17.  *     contributors may be used to endorse or promote products derived from
  18.  *     this software without specific prior written permission.
  19.  *
  20.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21.  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
  24.  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25.  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  26.  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27.  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28.  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  29.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30.  * POSSIBILITY OF SUCH DAMAGE.
  31.  *
  32.  * License 1.0
  33.  */
  34. package fr.paris.lutece.portal.service.util;

  35. import java.io.IOException;
  36. import java.io.InputStream;
  37. import java.io.UnsupportedEncodingException;
  38. import java.nio.charset.StandardCharsets;
  39. import java.security.InvalidKeyException;
  40. import java.security.MessageDigest;
  41. import java.security.NoSuchAlgorithmException;
  42. import java.util.Random;

  43. import fr.paris.lutece.portal.service.datastore.DatastoreService;
  44. import java.security.SecureRandom;

  45. import javax.crypto.Mac;
  46. import javax.crypto.spec.SecretKeySpec;

  47. /**
  48.  * The Class CryptoService.
  49.  */
  50. public final class CryptoService
  51. {
  52.     private static final int CONSTANT_CRYPTOKEY_LENGTH_BYTES = 32;
  53.     // Properties
  54.     private static final String PROPERTY_ENCODING = "lutece.encoding";
  55.     static final String PROPERTY_CRYPTO_KEY = "crypto.key";
  56.     static final String DSKEY_CRYPTO_KEY = "core." + PROPERTY_CRYPTO_KEY;

  57.     /**
  58.      * Private constructor
  59.      */
  60.     private CryptoService( )
  61.     {
  62.     }

  63.     /**
  64.      * Encrypt a data using an algorithm defined in lutece.properties
  65.      *
  66.      * @param strDataToEncrypt
  67.      *            The data to encrypt
  68.      * @param strAlgorithm
  69.      *            the algorithm
  70.      * @return The encrypted string
  71.      */
  72.     public static String encrypt( String strDataToEncrypt, String strAlgorithm )
  73.     {
  74.         String hash = null;
  75.         MessageDigest md = null;

  76.         try
  77.         {
  78.             md = MessageDigest.getInstance( strAlgorithm );
  79.         }
  80.         catch( NoSuchAlgorithmException e )
  81.         {
  82.             AppLogService.error( e.getMessage( ), e );
  83.         }

  84.         if ( md != null )
  85.         {
  86.             try
  87.             {
  88.                 hash = byteToHex( md.digest( strDataToEncrypt.getBytes( AppPropertiesService.getProperty( PROPERTY_ENCODING ) ) ) );
  89.             }
  90.             catch( UnsupportedEncodingException e )
  91.             {
  92.                 AppLogService.error( e.getMessage( ), e );
  93.             }
  94.         }
  95.         return hash;
  96.     }

  97.     /**
  98.      * Get a digest of the content of a stream
  99.      *
  100.      * @param stream
  101.      *            the stream containing the data to digest
  102.      * @param strAlgorithm
  103.      *            the digest Algorithm
  104.      * @return hex encoded digest string
  105.      * @see MessageDigest
  106.      * @since 6.0.0
  107.      */
  108.     public static String digest( InputStream stream, String strAlgorithm )
  109.     {
  110.         MessageDigest digest;
  111.         try
  112.         {
  113.             digest = MessageDigest.getInstance( strAlgorithm );
  114.         }
  115.         catch( NoSuchAlgorithmException e )
  116.         {
  117.             AppLogService.error( "{} not found", strAlgorithm, e );
  118.             return null;
  119.         }
  120.         byte [ ] buffer = new byte [ 1024];
  121.         try
  122.         {
  123.             int nNumBytesRead = stream.read( buffer );
  124.             while ( nNumBytesRead != -1 )
  125.             {
  126.                 digest.update( buffer, 0, nNumBytesRead );
  127.                 nNumBytesRead = stream.read( buffer );
  128.             }
  129.         }
  130.         catch( IOException e )
  131.         {
  132.             AppLogService.error( "Error reading stream", e );
  133.             return null;
  134.         }
  135.         return byteToHex( digest.digest( ) );
  136.     }

  137.     /**
  138.      * Get the cryptographic key of the application
  139.      *
  140.      * @return The cryptographic key of the application
  141.      */
  142.     public static String getCryptoKey( )
  143.     {
  144.         String strKey = DatastoreService.getDataValue( DSKEY_CRYPTO_KEY, null );
  145.         if ( strKey == null )
  146.         {
  147.             // no key as been generated for this application
  148.             strKey = AppPropertiesService.getProperty( PROPERTY_CRYPTO_KEY );
  149.             if ( strKey == null )
  150.             {
  151.                 // no legacy key exists. Generate a random one
  152.                 Random random = new SecureRandom( );
  153.                 byte [ ] bytes = new byte [ CONSTANT_CRYPTOKEY_LENGTH_BYTES];
  154.                 random.nextBytes( bytes );
  155.                 strKey = byteToHex( bytes );
  156.             }
  157.             DatastoreService.setDataValue( DSKEY_CRYPTO_KEY, strKey );
  158.         }
  159.         return strKey;
  160.     }

  161.     /**
  162.      * Get the HmacSHA256 of a message using the app crypto key. The UTF-8 representation of the key is used.
  163.      *
  164.      * @param message
  165.      *            the message. The mac is calculated from the UTF-8 representation
  166.      * @return the hmac as hex
  167.      * @since 6.0.0
  168.      */
  169.     public static String hmacSHA256( String message )
  170.     {
  171.         byte [ ] keyBytes = getCryptoKey( ).getBytes( StandardCharsets.UTF_8 );
  172.         final String strAlg = "HmacSHA256";
  173.         SecretKeySpec key = new SecretKeySpec( keyBytes, strAlg );

  174.         try
  175.         {
  176.             Mac mac = Mac.getInstance( strAlg );
  177.             mac.init( key );

  178.             return byteToHex( mac.doFinal( message.getBytes( StandardCharsets.UTF_8 ) ) );
  179.         }
  180.         catch( NoSuchAlgorithmException e )
  181.         {
  182.             throw new AppException( "Could not find " + strAlg + " algorithm which is supposed to be supported by Java", e );
  183.         }
  184.         catch( InvalidKeyException e )
  185.         {
  186.             throw new AppException( "The key should be valid", e );
  187.         }
  188.         catch( IllegalStateException e )
  189.         {
  190.             throw new AppException( e.getMessage( ), e );
  191.         }
  192.     }

  193.     /**
  194.      * Convert byte to hex
  195.      *
  196.      * @param bits
  197.      *            the byte to convert
  198.      * @return the hex
  199.      */
  200.     private static String byteToHex( byte [ ] bits )
  201.     {
  202.         if ( bits == null )
  203.         {
  204.             return null;
  205.         }

  206.         // encod(1_bit) => 2 digits
  207.         StringBuilder hex = new StringBuilder( bits.length * 2 );

  208.         for ( int i = 0; i < bits.length; i++ )
  209.         {
  210.             if ( ( (int) bits [i] & 0xff ) < 0x10 )
  211.             {
  212.                 // 0 < .. < 9
  213.                 hex.append( "0" );
  214.             }

  215.             // [(bit+256)%256]^16
  216.             hex.append( Integer.toString( (int) bits [i] & 0xff, 16 ) );
  217.         }

  218.         return hex.toString( );
  219.     }
  220. }