CryptoService.java
- /*
- * Copyright (c) 2002-2022, City of Paris
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice
- * and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice
- * and the following disclaimer in the documentation and/or other materials
- * provided with the distribution.
- *
- * 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * License 1.0
- */
- package fr.paris.lutece.portal.service.util;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.UnsupportedEncodingException;
- import java.nio.charset.StandardCharsets;
- import java.security.InvalidKeyException;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.util.Random;
- import fr.paris.lutece.portal.service.datastore.DatastoreService;
- import java.security.SecureRandom;
- import javax.crypto.Mac;
- import javax.crypto.spec.SecretKeySpec;
- /**
- * The Class CryptoService.
- */
- public final class CryptoService
- {
- private static final int CONSTANT_CRYPTOKEY_LENGTH_BYTES = 32;
- // Properties
- private static final String PROPERTY_ENCODING = "lutece.encoding";
- static final String PROPERTY_CRYPTO_KEY = "crypto.key";
- static final String DSKEY_CRYPTO_KEY = "core." + PROPERTY_CRYPTO_KEY;
- /**
- * Private constructor
- */
- private CryptoService( )
- {
- }
- /**
- * Encrypt a data using an algorithm defined in lutece.properties
- *
- * @param strDataToEncrypt
- * The data to encrypt
- * @param strAlgorithm
- * the algorithm
- * @return The encrypted string
- */
- public static String encrypt( String strDataToEncrypt, String strAlgorithm )
- {
- String hash = null;
- MessageDigest md = null;
- try
- {
- md = MessageDigest.getInstance( strAlgorithm );
- }
- catch( NoSuchAlgorithmException e )
- {
- AppLogService.error( e.getMessage( ), e );
- }
- if ( md != null )
- {
- try
- {
- hash = byteToHex( md.digest( strDataToEncrypt.getBytes( AppPropertiesService.getProperty( PROPERTY_ENCODING ) ) ) );
- }
- catch( UnsupportedEncodingException e )
- {
- AppLogService.error( e.getMessage( ), e );
- }
- }
- return hash;
- }
- /**
- * Get a digest of the content of a stream
- *
- * @param stream
- * the stream containing the data to digest
- * @param strAlgorithm
- * the digest Algorithm
- * @return hex encoded digest string
- * @see MessageDigest
- * @since 6.0.0
- */
- public static String digest( InputStream stream, String strAlgorithm )
- {
- MessageDigest digest;
- try
- {
- digest = MessageDigest.getInstance( strAlgorithm );
- }
- catch( NoSuchAlgorithmException e )
- {
- AppLogService.error( "{} not found", strAlgorithm, e );
- return null;
- }
- byte [ ] buffer = new byte [ 1024];
- try
- {
- int nNumBytesRead = stream.read( buffer );
- while ( nNumBytesRead != -1 )
- {
- digest.update( buffer, 0, nNumBytesRead );
- nNumBytesRead = stream.read( buffer );
- }
- }
- catch( IOException e )
- {
- AppLogService.error( "Error reading stream", e );
- return null;
- }
- return byteToHex( digest.digest( ) );
- }
- /**
- * Get the cryptographic key of the application
- *
- * @return The cryptographic key of the application
- */
- public static String getCryptoKey( )
- {
- String strKey = DatastoreService.getDataValue( DSKEY_CRYPTO_KEY, null );
- if ( strKey == null )
- {
- // no key as been generated for this application
- strKey = AppPropertiesService.getProperty( PROPERTY_CRYPTO_KEY );
- if ( strKey == null )
- {
- // no legacy key exists. Generate a random one
- Random random = new SecureRandom( );
- byte [ ] bytes = new byte [ CONSTANT_CRYPTOKEY_LENGTH_BYTES];
- random.nextBytes( bytes );
- strKey = byteToHex( bytes );
- }
- DatastoreService.setDataValue( DSKEY_CRYPTO_KEY, strKey );
- }
- return strKey;
- }
- /**
- * Get the HmacSHA256 of a message using the app crypto key. The UTF-8 representation of the key is used.
- *
- * @param message
- * the message. The mac is calculated from the UTF-8 representation
- * @return the hmac as hex
- * @since 6.0.0
- */
- public static String hmacSHA256( String message )
- {
- byte [ ] keyBytes = getCryptoKey( ).getBytes( StandardCharsets.UTF_8 );
- final String strAlg = "HmacSHA256";
- SecretKeySpec key = new SecretKeySpec( keyBytes, strAlg );
- try
- {
- Mac mac = Mac.getInstance( strAlg );
- mac.init( key );
- return byteToHex( mac.doFinal( message.getBytes( StandardCharsets.UTF_8 ) ) );
- }
- catch( NoSuchAlgorithmException e )
- {
- throw new AppException( "Could not find " + strAlg + " algorithm which is supposed to be supported by Java", e );
- }
- catch( InvalidKeyException e )
- {
- throw new AppException( "The key should be valid", e );
- }
- catch( IllegalStateException e )
- {
- throw new AppException( e.getMessage( ), e );
- }
- }
- /**
- * Convert byte to hex
- *
- * @param bits
- * the byte to convert
- * @return the hex
- */
- private static String byteToHex( byte [ ] bits )
- {
- if ( bits == null )
- {
- return null;
- }
- // encod(1_bit) => 2 digits
- StringBuilder hex = new StringBuilder( bits.length * 2 );
- for ( int i = 0; i < bits.length; i++ )
- {
- if ( ( (int) bits [i] & 0xff ) < 0x10 )
- {
- // 0 < .. < 9
- hex.append( "0" );
- }
- // [(bit+256)%256]^16
- hex.append( Integer.toString( (int) bits [i] & 0xff, 16 ) );
- }
- return hex.toString( );
- }
- }