/*
* Copyright (c) 2013-2014 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.werval.runtime;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Random;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import io.werval.api.Crypto;
import io.werval.api.exceptions.WervalException;
import io.werval.runtime.util.Lazy;
import io.werval.util.Hashids;
import io.werval.util.Hex;
import io.werval.util.Reflectively;
/**
* Cryptography service instance.
*/
public class CryptoInstance
implements Crypto
{
private final byte[] secretBytes;
private final Charset charset;
private final Lazy<Hashids> hashids;
public CryptoInstance( String secret, Charset charset )
{
try
{
this.secretBytes = Hex.decode( secret.toCharArray() );
}
catch( IllegalArgumentException ex )
{
throw new WervalException( "Wrong Application Secret: " + ex.getMessage(), ex );
}
if( this.secretBytes.length < 32 )
{
throw new WervalException( "Weak Application Secret: must be at least 256bits long" );
}
this.charset = charset;
this.hashids = Lazy.of( () -> new Hashids( secret, 4 ) );
}
@Override
public byte[] secret()
{
// Defensive copy
byte[] secret = new byte[ secretBytes.length ];
System.arraycopy( secretBytes, 0, secret, 0, secretBytes.length );
return secret;
}
@Override
public byte[] newSecret()
{
return newRandomSecret256Bits();
}
@Override
public String newSecretHex()
{
return newRandomSecret256BitsHex();
}
@Override
public String newSecretBase64()
{
return Base64.getEncoder().encodeToString( newRandomSecret256Bits() );
}
public static String newRandomSecret256BitsHex()
{
return Hex.encode( newRandomSecret256Bits() );
}
@Reflectively.Invoked( by = "DevShell" )
public static String newWeaklyRandomSecret256BitsHex()
{
byte[] bytes = new byte[ 32 ];
new Random().nextBytes( bytes );
return Hex.encode( bytes );
}
private static byte[] newRandomSecret256Bits()
{
try
{
byte[] bytes = new byte[ 32 ];
SecureRandom.getInstanceStrong().nextBytes( bytes );
return bytes;
}
catch( NoSuchAlgorithmException ex )
{
throw new InternalError( ex.getMessage(), ex );
}
}
@Override
public byte[] hmacSha256( byte[] message )
{
return hmacSha256( message, secretBytes );
}
@Override
public byte[] hmacSha256( byte[] message, byte[] secret )
{
try
{
SecretKeySpec signingKey = new SecretKeySpec( secret, "HmacSHA256" );
Mac mac = Mac.getInstance( "HmacSHA256" );
mac.init( signingKey );
return mac.doFinal( message );
}
catch( NoSuchAlgorithmException | InvalidKeyException | IllegalStateException ex )
{
throw new WervalException( "Unable to HMAC message", ex );
}
}
@Override
public String hmacSha256Hex( CharSequence message )
{
return Hex.encode(
hmacSha256(
message.toString().getBytes( charset ),
secretBytes
)
);
}
@Override
public String hmacSha256Hex( CharSequence message, String secret )
{
return Hex.encode(
hmacSha256(
message.toString().getBytes( charset ),
Hex.decode( secret.toCharArray() )
)
);
}
@Override
public byte[] sha256( byte[] message )
{
try
{
return MessageDigest.getInstance( "SHA-256" ).digest( message );
}
catch( NoSuchAlgorithmException ex )
{
throw new WervalException( "Unable to SHA256 message", ex );
}
}
@Override
public byte[] sha256( CharSequence message )
{
return sha256( message.toString().getBytes( charset ) );
}
@Override
public String sha256Hex( CharSequence message )
{
return Hex.encode( sha256( message ) );
}
@Override
public String sha256Base64( CharSequence message )
{
return Base64.getEncoder().encodeToString( sha256( message ) );
}
@Override
public Hashids hashids()
{
return hashids.get();
}
}