Monday, May 16, 2022

Java Tutorial: Timer and TimerTask

Chapters

Overview

Timer is used to scheule tasks that are provided by TimerTask.In java 1.5 and above, it's preferrable to use ScheduledThreadPoolExecutor class. Java provides an easy-to-use instance of ScheduledThreadPoolExecutor which can be instantiated by invoking newScheduledThreadPool() method.

In java below 1.5, concurrent package was not introduced. Therefore, ScheduledThreadPoolExecutor isn't available in those versions.

Run Timer After a Certain Delay

We can use Timer to perform a task after a certain delay. Take a look at this example.
import java.util.Timer;
import java.util.TimerTask;

public class SampleClass{
  private static final long initTime = 
  System.currentTimeMillis();
  private static volatile boolean endMain = false;
  
  public static void main(String[] args) {
  
    TimerTask myTask = new TimerTask(){
      
      @Override
      public void run(){
        System.out.println("Elapsed Time");
        System.out.println(System.currentTimeMillis() - 
        initTime);
        endMain = true;
      }
    };
    Timer timer = new Timer("MyTimer", true);
    
    System.out.println("Task starts after 2 seconds...");
    long delay = 2000L; //2 seconds
    timer.schedule(myTask, delay);
    
    //block main thread while the scheduled task is not
    //complete yet
    while(!endMain);
    
  }
}

Result(may vary)
Task starts after 2 seconds...
Elapsed Time
2000
First off, TimerTask implements Runnable interface and tasks are run via run() method. Thus, we need to override run() method and write our specified task inside it. Timer(String name, boolean isDaemon) creates a new timer whose associated thread has the specified name, and may be specified to run as a daemon thread.

schedule(TimerTask task, long delay) creates a one-shot schedule for task in TimerTask after a specified delay. Each instance of Timer is associated with a single background thread that executes tasks scheduled by the timer. If you want to set your Timer with a delay based on date & time, use schedule(TimerTask task, Date time).

Schedule Timer with a Fixed Delay or Fixed Rate

If we want to schedule tasks at fixed-delay, we use schedule(TimerTask task, long delay, long period) constructor. for initial delay based on date & time, use schedule(TimerTask task, Date firstTime, long period) constructor. I explained the concepts of fixed delay and fixed rate in this article.

This example demonstrates Timer with fixed-delay.
import java.util.Timer;
import java.util.TimerTask;

public class SampleClass{
  private static final long initTime = 
  System.currentTimeMillis();
  private static volatile boolean endMain = false;
  
  public static void main(String[] args) {
  
    TimerTask myTask = new TimerTask(){
      private int num = 0;
      
      @Override
      public void run(){
        System.out.println("Elapsed Time");
        System.out.println(System.currentTimeMillis() - 
        initTime);
        ++num;
        if(num == 5)
          endMain = true;
      }
    };
    Timer timer = new Timer("MyTimer", true);
    
    System.out.println("Task starts after 2 seconds...");
    long delay = 2000L; //2 seconds
    timer.schedule(myTask, delay, 1000L);
    
    //block main thread while the scheduled task is not
    //complete yet
    while(!endMain);
    
  }
}

Result(may vary)
Elapsed Time
2000
Elapsed Time
3000
Elapsed Time
4000
Elapsed Time
5000
Elapsed Time
6000
If you want to schedule your timer at fixed rate, you can use one of these two methods:
scheduleAtFixedRate(TimerTask task, long delay, long period)
scheduleAtFixedRate(TimerTask task, Date firstTime, long period)

Also, Timer and TimerTask can be cancelled. cancel method in TimerTask cancels all running tasks of this TimerTask and Timer class can't reschedule the task anymore.

cancel method in Timer class cancels the timer. This means that we can't schedule any task in the timer anymore. cancel method in Timer class may not terminate its background thread right after the moment it was cancelled.

Monday, May 9, 2022

Java Tutorial: currentTimeMillis() and nanoTime()

Chapters

currentTimeMillis() and nanoTime()

currentTimeMillis() returns the current time of our machine in milliseconds. The measured time starts from midnight, January 1, 1970 UTC (epoch time) up to the current date in our machine. Take note that the granularity of the value depends on the underlying operating system and may be larger. For example, many operating systems measure time in units of tens of milliseconds.

nanoTime() returns the current time of our machine in nanoseconds. The measured time starts from midnight, January 1, 1970 UTC (epoch time) up to the current date/time in our machine.

Take note that the method provides nanosecond precision, but not necessarily nanosecond resolution. This means that the returned value is in nanosecond digits but the returned value itself maybe larger than a nanosecond. The resolution of nanoTime() is at least as good as that of currentTimeMillis().

For high precision timers, use nanoTime(). Otherwise, currentTimeMillis() should suffice. These two methods can be found in the System class.

This example demonstrates currentTimeMillis().
public class SampleClass{

  public static void main(String[] args){
      long currentTime = System.currentTimeMillis();
      MyTimer timer = new MyTimer(
      currentTime, currentTime + 3000L);
      timer.start();
  }
}

class MyTimer extends Thread{
  long time = 0L;
  long initTime = 0L;
  
  MyTimer(long initTime, long time){
    this.initTime = initTime;
    this.time = time;
    
    System.out.println("Starting Time");
    System.out.println(initTime);
    System.out.println("Target Time");
    System.out.println(time);
    System.out.println();
  }
  
  @Override
  public void run(){
    long timeMillis = initTime;
    long targetTime = time;
    
    if(targetTime < timeMillis){
      System.out.println("Target time "+
      "is less than current time!");
      return;
    }
    
    while(timeMillis < targetTime){
      System.out.println("Remaining Time");
      System.out.println((targetTime - timeMillis) + 
      " milliseconds.");
      System.out.println(
      ((targetTime - timeMillis)/1000) + //convert millis to seconds
      " seconds.");
      System.out.println();
      
      try{ Thread.sleep(900L); }
      catch(InterruptedException e){
        e.printStackTrace();
      }
      timeMillis = System.currentTimeMillis();
    }
    System.out.println("\nDone!");
  }
}

Result(may vary)
Starting Time
1652089057223
Target Time
1652089060223

Remaining Time
3000 milliseconds.
3 seconds.

Remaining Time
2085 milliseconds.
2 seconds.

Remaining Time
1183 milliseconds.
1 seconds.

Remaining Time
280 milliseconds.
0 seconds.

Done!
In MyTimer constructor, initTime parameter is the starting time in our timer. We use System.currentTimeMillis() to get the current time once our program runs and use that as the starting time. time parameter is our target time, we use the current time plus additional milliseconds to get our desired target time.

In the example above, the distance between the starting time and target time is 3000 millis. Now, try to use nanoTime() instead of currentTimeMillis() in the example above.

currentTimeMillis() and nanoTime() have many uses. Another use of them is creating a basic performance test. Take a look at this example.
import java.util.Random;

public class SampleClass{

  public static void main(String[] args){
    Random random = new Random();
    
    System.out.println("StartTime");
    long startTime = System.nanoTime();
    System.out.println(startTime);
    
    double num = 0;
    for(int i = 0; i < 1000000; i++)
      num = i/random.nextInt();
    
    System.out.println("EndTime");
    long endTime = System.nanoTime();
    System.out.println(endTime);
    System.out.println();
    
    System.out.println("Elapsed Time");
    double elapsed = endTime - startTime;
    System.out.println(elapsed + " nanoseconds");
    //convert nano to millis
    //1000 nanosec = 1 microsec
    //1000 microsec = 1 millisec
    System.out.println(elapsed/1000/1000 + " milliseconds");
  }
}

Result(may vary)
1.3453649e7 nanoseconds
13.45364899 milliseconds

Sunday, May 8, 2022

Java Tutorial: Base64 Class

Chapters

Overview

Base64 class consists exclusively of static methods for obtaining encoders and decoders for the Base64 encoding scheme. Base64 encodes binary data into 6-bit Base64 characters.

Base64 is useful when we want to transmit binary data over channels that only support text content. For example, Base64 is also widely used for sending e-mail attachments because SMTP, the widely used electronic mail protocol, can only transport 7-bit ASCII characters.

Base64 is widely used in World Wide Web. One of its usage is embedding image files in HTML. There are the types of Base64 that java supports.
  • Basic
  • URL and Filename safe
  • MIME
The length of Base64 characters is 6 bits. A 65-character subset of US-ASCII is used, enabling 6 bits to be represented per printable character. Base64 characters can be found in this table.

Base64 encoding length should be multiple of four. If this is not met, the encoder will add "=" as padding to compensate for the lack of characters. If you don't want the encoder to add padding, invoke withoutPadding method first before encoding binary data. For example:

String encoded = Base64.getEncoder().withoutPadding().encodeToString(input.getBytes("UTF-8"));


Basic Encoder

Basic encoder just encodes data as-is. Basic decoder rejects any characters that are not part of Base64 characters. This example demonstrates encoding data to Base64 and decoding it back to its original format.
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class SampleClass{

  public static void main(String[] args) throws
                     UnsupportedEncodingException{
    
    String input = "\u00e9zebra!!";
    System.out.println("Original");
    System.out.println(input);
    System.out.println();
    
    String encoded = Base64.getEncoder().
    encodeToString(input.getBytes("UTF-8"));
    System.out.println("Encoded");
    System.out.println(encoded);
    System.out.println();
    
    byte[] decodedBytes = 
    Base64.getDecoder().decode(encoded);
    String decodedStr = 
    new String(decodedBytes, "UTF-8");
    System.out.println("Decoded");
    System.out.println(decodedStr);
    System.out.println();
    
  }
}

Result
Original
ézebra!!

Encoded
w616ZWJyYSEh

Decoded
ézebra!!
Base64.getEncoder returns a Base64.Encoder instance using basic type encoder. Base64.getDecoder returns a Base64.Decoder instance using basic type decoder.

URL and Filename-safe

URL and Filename-safe Base64 encoder encodes data with URL-friendly and filename-friendly characters. In this encoder, "+" is replaced by "-" and "/" is replaced by "_". Those characters that are replaced have special meanings to URLs and filesystems. Those characters with special meanings may mess up the encoded data. Take a look at this table to know more about the differences between Base64 encoders.

This example demonstrates encoding URL using Base64.
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class SampleClass{

  public static void main(String[] args) throws
                     UnsupportedEncodingException{
    
    String input = "https://www.google.com/";
    System.out.println("Original");
    System.out.println(input);
    System.out.println();
    
    String encoded = Base64.getUrlEncoder().
    encodeToString(input.getBytes("UTF-8"));
    System.out.println("Encoded");
    System.out.println(encoded);
    System.out.println();
    
    byte[] decodedBytes = 
    Base64.getUrlDecoder().decode(encoded);
    String decodedStr = 
    new String(decodedBytes, "UTF-8");
    System.out.println("Decoded");
    System.out.println(decodedStr);
    System.out.println();
    
  }
}

Result
Original
https://www.google.com/

Encoded
aHR0cHM6Ly93d3cuZ29...

Decoded
https://www.google.com/

Base64.getUrlEncoder returns a Base64.Encoder instance using URL and filename-safe type encoder. Base64.getUrlDecoder returns a Base64.Decoder instance using URL and filename-safe type decoder.

MIME

Multipurpose Internet Mail Extensions(MIME) is an Internet standard that extends the format of email messages to support text in character sets other than ASCII, as well as attachments of audio, video, images, and application programs.

Base64 alphabet is supported by MIME character set. Thus, Base64 MIME encoder is MIME-friendly. This example demonstrates encoding using Base64 MIME encoder.
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Base64;

public class SampleClass{

  public static void main(String[] args) throws
                     UnsupportedEncodingException,
                     IOException{
    
    File file = new File("files/image.png");
    byte[] fileBytes = 
    Files.readAllBytes(file.toPath());
    
    System.out.println("Original");
    for(byte b : fileBytes)
      System.out.print(b + " ");
    System.out.println();
    
    String encoded = Base64.getMimeEncoder().
    encodeToString(fileBytes);
    System.out.println("Encoded");
    System.out.println(encoded);
    System.out.println();
    
    byte[] decodedBytes = 
    Base64.getMimeDecoder().decode(encoded);
    String decodedStr = 
    new String(decodedBytes, "UTF-8");
    System.out.println("Decoded");
    for(byte b : fileBytes)
      System.out.print(b + " ");
  }
}

Result
Original
-119 80 78 71 13 10 26...
Encoded
iVBORw0Kggo...

Decoded
-119 80 78 71 13 10 26...
Base64.getMimeEncoder returns a Base64.Encoder instance using MIME type encoder. Base64.getMimeDecoder returns a Base64.Decoder instance using MIME type decoder. The length of lines in the output is no longer than 76 characters. Also, each line end with CR+LF(\r\n).

If you want to change the character per length and line separators, invoke getMimeEncoder(int lineLength, byte[] lineSeparator). In the example above, the encoded bytes of the image file can be used in HTML. For example:
<div>
  <p>Taken from wikpedia</p>
  <img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA
    AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
        9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />
</div>

Friday, May 6, 2022

Java Tutorial: Cryptography

Chapters

Introduction

In computer programming, Cryptography is a way of hiding data(passwords, messages, etc.) from third parties(adversaries) by securing the communication process between two entities. Cryptographic functions can be found in two java librarties: Java Cryptography Architecture(JCA) and Java Cryptography Extension(JCE).

JCA is tightly integrated with Java core API and provides basic cryptographic functions whereas JCE provides advanced cryptographic functions. Classes belonging to JCA and JCE are called engines. JCA engines are located at java.security package and JCE engines are located at javax.crypto packages. In the past, JCE is separated from JCA due to US export policies. Over time, This export control has been relaxed and in the present JCE becomes part of Java SE.

JCA and JCE provide functionalities. The actual implementations of those functions are provided by providers. Java has default providers we can use right away. Invoke getProviders() method from Security class in order to see all available providers in your java platform. The order of the providers in the array is their preference order.

The method returns Provider objects that hold information about cryptography providers. If you want to add new provider to your platform, invoke addProvider method from Security class.

For example:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;

public class ProviderExample{

  public static void main(String[] args){
    Security.addProvider(new BouncyCastleProvider());
  }
}
BouncyCastle is one of the popular cryptography providers that can be used in java. It was developed by an Australian charitable organization thus, US law restrictions do not apply to this provider. In the course of this tutorial, we're going to use the default providers provided by java.

Another way of using a third-party provider is to register it in the java.security file. In JDK8 and lower, the file is located in the directory where you installed java at jre\lib\security\java.security. Place the provider JAR in lib\ext

In JDK9 and above, the file is located in the directory where you installed java at conf\security\java.security. Place the provider JAR in the classpath.

Once you open java.security file, look at this section:
...
#
# List of providers and their preference orders (see above):
#
security.provider.1=SUN
security.provider.2=SunRsaSign
security.provider.3=SunEC
security.provider.4=SunJSSE
...
Then, add your third-party provider in the list:
...
#
# List of providers and their preference orders (see above):
#
security.provider.1=SUN
security.provider.2=SunRsaSign
security.provider.3=SunEC
security.provider.4=SunJSSE
security.provider.5=third-party-provider-name
...
In this tutorial, I'll be discussing Hashing, Symmetric Encryption, Asymmetric Encryption and Digital Signature. Java can also create digital certificate but I won't cover cerfiticates in this tutorial.

Hashing

Hashing or cryptographic hash function is a process of mapping data("message") with arbitrary size to an array of bits with fixed size (also called "hash value", "hash", "message digest").

Hashing is a one-way function. It means that the output is unfeasible to invert or reverse. Also, hashing must be deterministic. It means that messages that are the same have equivalent hash. On the other hand, a slight change to a message creates a different hash for the message.

This example demonstrates hashing in java.
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.*;

public class SampleClass{

  public static void main(String[] args) throws
                     NoSuchAlgorithmException,
                     UnsupportedEncodingException{
    MessageDigest digestInstance = 
    MessageDigest.getInstance("SHA-256");
    String message1 = "Secret Message!";
    String message2 = 
    "The big brown fox jumps over the lazy doge";
    
    byte[] input = message1.getBytes("UTF-8");
    byte[] hash = digestInstance.digest(input);
    
    System.out.println("Message1: " + message1);
    System.out.println("Hash Value: " + 
    new BigInteger(1, hash).toString(16));
    System.out.println("Hash Length");
    System.out.println(hash.length + " bytes");
    System.out.println((hash.length*8) + " bits");
    
    input = message2.getBytes("UTF-8");
    hash = digestInstance.digest(input);
    
    System.out.println();
    System.out.println("Message2: " + message2);
    System.out.println("Hash Value: " + 
    new BigInteger(1, hash).toString(16));
    System.out.println("Hash Length");
    System.out.println(hash.length + " bytes");
    System.out.println((hash.length*8) + " bits");
  }
}

Result
Message1: Secret Message!
Hash Value: 1bf7aa187ccdf...
Hash Length
32 bytes
256 bits

Message2: The big brown fox jumps over the lazy doge
Hash Value: ec74b616322364f...
Hash Length
32 bytes
256 bits
MessageDigest provides cryptographic hash function with cryptographic hash algorithm such as SHA-256. Take a look at this article to know the supported cryptographic hash algorithms of MessageDigest.

SHA(Secure Hash Algorithm)-256 is a cryptographic hash algorithm that produces 256-bit hash value. This algorithm produces more bits than the old SHA algorithms such as SHA-0 and SHA-1 which produce 160-bit hash value. Thus SHA-256 is more secure than its old counterparts. If you want more secure algorithms, use SHA3 algorithms.

However, SHA-2 algorithms such as SHA-256, SHA-384, SHA-512 and SHA-224 are better than SHA3 algorithms in terms of performance. MD2 and MD5 are old and less secure than SHA-2 and SHA3 algorithms. Thus, it's not recommended to be used to secure data.

Although, MD5 is still used for checking the integrity of a file. Take note that files consist of bytes. Thus, we can get those bytes and put them in MessageDigest instance. MessageDigest.getInstance method returns a MessageDigest object with the specified hash algorithm. getBytes(String charsetName) method returns the byte structure of a java object or file in array form. The bytes are formatted based on charsetName.

digest method completes the hash computation by performing final operations such as padding. The digest is reset after this call is made. Padding is an additional bit added to the hash value if the input bits are less than the bits that a hash algorithm generates. That's why, In the example above, even Message1 is less than 256 bits, the hash value is still 256 bits.

We can use hash values to ensure that the expected message that the received message of a receiver is equivalent to the sent message of the sender. Take a look at this example
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.*;
import java.util.Arrays;

public class SampleClass{

  public static void main(String[] args) throws
                     NoSuchAlgorithmException,
                     UnsupportedEncodingException{
    /*sender*/
    MessageDigest senderDigest = 
    MessageDigest.getInstance("SHA-256");
    String message = "My Message!";
    
    byte[] senderInput = message.getBytes("UTF-8");
    byte[] senderHash = 
    senderDigest.digest(senderInput);
    System.out.println("Sender Hash Value: " +
    new BigInteger(1, senderHash).toString(16));
    
    /*receiver*/
    MessageDigest receiverDigest = 
    MessageDigest.getInstance("SHA-256");
    String expectedMsg = "My Message!";
    
    byte[] receiverInput = 
    expectedMsg.getBytes("UTF-8");
    byte[] receiverHash = 
    receiverDigest.digest(receiverInput);
    
    System.out.println("Receiver Hash Value: " +
    new BigInteger(1, receiverHash).toString(16));
    System.out.println("Sender and Receiver" +
    " hash values are equal?");
    System.out.println(
    Arrays.equals(senderHash, receiverHash));
  }
}

Result
Sender Hash Value: d2f02a685b80a087de...
Receiver Hash Value: d2f02a685b80a087de...
Sender and Receiver hash values are equal?
true
The example above is analogous to password hashing. When we create an application, we take the hash value of the password instead of the password itself and store the hash value in the database. This implementation hides passwords from employees and prying eyes.

Hashing has vulnerabilities such as brute-force search and rainbow-table. Although, those vulnerabilities may take a very long time breaking hash algorithms. We're talking about years or decades.

To defend against these vulnerabilities, we need to add a salt. To put it simply, a salt is just a random data added to the input. The salt is stored along with the password hash. During verification, the stored salt is included in the hash process along with the input. Then, the generated hash will be compared with the stored hash.

If you want the hash of multiple data, we use the update method. Take a look at this example.
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.math.BigInteger;

public class SampleClass{

  public static void main(String[] args) throws
                     NoSuchAlgorithmException,
                     UnsupportedEncodingException{
    MessageDigest digest = 
    MessageDigest.getInstance("SHA-512");
    String message1 = "Message1";
    String message2 = "Message2";
    
    digest.update(message1.getBytes("UTF-8"));
    digest.update(message2.getBytes("UTF-8"));
    byte[] hash = digest.digest();
    
    System.out.println("Hash value: " + 
    new BigInteger(1, hash).toString(16));
    System.out.println("Hash Length");
    System.out.println(hash.length + " bytes");
    System.out.println((hash.length*8) + " bits");
  }
}

Result
Hash value: dae036f171ffd578ff...
Hash length
64 bytes
512 bits
Symmetric Encryption

Symmetric encryption uses a single key for encrypting and decrypting data. An unencrypted data is called "cleartext" or "plaintext". The encrypted data is called "ciphertext". This example demonstrates symmetric encryption in java.
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.BadPaddingException;

public class SampleClass{

  public static void main(String[] args) throws
                     NoSuchAlgorithmException,
                     InvalidKeyException,
                     IllegalBlockSizeException,
                     NoSuchPaddingException,
                     BadPaddingException,
                     UnsupportedEncodingException{
    KeyGenerator keygen = 
    KeyGenerator.getInstance("AES");
    
    keygen.init(192);
    SecretKey key = keygen.generateKey();
    System.out.println("Generated Key");
    System.out.println(
    new BigInteger(1, key.getEncoded()).toString(16));
    
    byte[] input = 
    "The big brown fox jumps over a lazy dog."
    .getBytes("UTF-8");
    System.out.println("Plain text");
    System.out.println(new String(input));
    
    Cipher cipher = 
    Cipher.getInstance("AES/ECB/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] encryptedData = cipher.doFinal(input);
    System.out.println("Ciphertext");
    System.out.println(
    new BigInteger(1, encryptedData).toString(16));
    
    cipher.init(Cipher.DECRYPT_MODE, key);
    byte[] decryptedData = 
    cipher.doFinal(encryptedData);
    System.out.println("Decrypted Data");
    System.out.println(new String(decryptedData));
  }
}

Result(may vary)
Generated Key
57197a794884a...
Plain Text
The big brown fox jumps over a lazy dog.
Ciphertext
2968b36c3699c5a12843...
Decrypted Data
The big brown fox jumps over a lazy dog.
KeyGenerator provides the functionality of a secret (symmetric) key generator. KeyGenerator objects are reusable, i.e., after a key has been generated, the same KeyGenerator object can be re-used to generate further keys. KeyGenerator.getInstance(String algorithm) method returns a KeyGenerator object with the specified algorithm for generating a key.

Available algrorithms for KeyGenerator can be found in this article. Take note that DES(Data Encryption Standard) is discouraged to be used because it's old and not secure anymore.

init(int keysize) method initializes this key generator for a certain keysize using the provider's SecureRandom. AES(Advanced Encryption Standard) supports 128/192/256 bits keysize. AES uses Substitution–permutation network.

To put it simply, AES scrambles bits in multiple rounds. Key size of AES determines the number of round that is going to be used. 128-bit key size implements 10 rounds. 192-bit implements 12 rounds. 256-bit implements 14 rounds. A key with higher key size is more secure. However, key with higher key size takes too long to encrypt/decrypt data. Thus, degrading our application's performance. For standard applications, at the time I write this tutorial, 128-bit key size should suffice.

generateKey method returns SecretKey compatible with the specified algorithm in KeyGenerator object. A key is a string of characters used within an encryption algorithm for altering data so that it appears random.

Cipher provides the functionality of a cryptographic cipher for encryption and decryption. We use Cipher class to encrypt and decrypt data. getInstance(String transformation) returns a Cipher object that implements the specified transformation.

A transformation is a string that describes the operation (or set of operations) to be performed on the given input, to produce some output. A transformation always includes the name of a cryptographic algorithm (e.g., AES), and may be followed by a feedback mode and padding scheme.

A transformation string can be one of these:
  • "algorithm/mode/padding"
  • "algorithm"
In the latter case, provider-specific default values for the mode and padding scheme are used. Before we discuss the mode in a transformation, we need to know two types of Ciphers: Stream Cipher and Block Cipher.

Stream Cipher encrypts the digits (typically bytes), or letters (in substitution ciphers) of a message one at a time. An example is ChaCha20. Substitution ciphers are well-known ciphers, but can be easily decrypted using a frequency table.

Block Cipher takes a number of bits and encrypt them as a single unit, padding the plaintext so that it is a multiple of the block size. The Advanced Encryption Standard (AES) algorithm, approved by NIST in December 2001, uses 128-bit blocks. To put it simply, Block cipher divides the number of bits of plaintext into blocks based on block size. Each block size of blocks is equal.

Now, mode or block cipher mode determines how blocks are encrypted. Some modes(such as CFB and OFB) can encypt and decrypt blocks that are smaller than the actual cipher's block size. There are different types of modes and I'm not gonna explain all of them in this tutorial. You can read this article if you're interested to learn more about them.

ECB(Electronic Code Book) is the simplest cipher block mode because unlike other modes, this mode directly encrypt and decrypt blocks without additional procedures. However, this mode is less secure than others. This mode is bad at hiding data patterns and susceptible to replay attacks since this mode encrypts and decrypts blocks the same way.

padding is any of a number of distinct practices which all include adding data to the beginning, middle, or end of a message prior to encryption. In classical cryptography, padding may include adding nonsense phrases to a message to obscure the fact that many messages end in predictable ways.

PKCS5Padding is commonly used padding type that is provided by default providers. In PKCS5Padding, Padding is in whole bytes. The value of each added byte is the number of bytes that are added, i.e. N bytes, each of value N are added. The number of bytes added will depend on the block boundary to which the message needs to be extended.

The padding will be one of:
01
02 02
03 03 03
04 04 04 04
05 05 05 05 05
06 06 06 06 06 06
etc.
Example: In the following example the block size is 8 bytes and padding is required for 4 bytes.
... | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |
In java, we can use "NoPadding" in the string transformation if we're going to provide our own padding. Otherwise, we need to use one of the padding types available to our platform.

More information about padding types can be found in this article. More information about PKCS(Public Key Cryptography Standards) can be found in this article. Take note that ISO10126Padding is not encouraged to be used anymore because it's widthrawn in 2007.

init(int opmode, Key key) initializes a Cipher object with specified operation and key. The cipher is initialized for one of the following four operations: encryption, decryption, key wrapping or key unwrapping, depending on the value of opmode. This method form uses the provider's SecureRandom if the Cipher object needs random bytes.

Let's focus on encrypting and decrypting data. To initialize a Cipher object in "encrypt" mode, put Cipher.ENCRYPT_MODE in the argument list with the selected key. To initialize a Cipher object in "decrypt" mode, put Cipher.DECRYPT_MODE in the argument list with selected key.

doFinal(byte[] input) executes the operation of the Cipher object that invokes this method. If you want to encrypt multi-part data, use update(byte[] input) method per data and then invoke doFinal() method to finalize the input and execute the selected operation.

Next, I'm gonna demonstrate another cipher block mode which is the CBC(Cipher Block Chaining). This mode is more secure than ECB and is commonly used. In CBC mode, each block of plaintext is XORed with the previous ciphertext block before being encrypted. This way, each ciphertext block depends on all plaintext blocks processed up to that point. To make each message unique, an initialization vector(IV) must be used in the first block.

This example demonstrates CBC mode.
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.BadPaddingException;
import javax.crypto.spec.IvParameterSpec;

public class SampleClass{

  public static void main(String[] args) throws
                     NoSuchAlgorithmException,
                     InvalidAlgorithmParameterException,
                     InvalidKeyException,
                     IllegalBlockSizeException,
                     NoSuchPaddingException,
                     BadPaddingException,
                     UnsupportedEncodingException{
                     
    KeyGenerator keygen = 
    KeyGenerator.getInstance("AES");
    
    keygen.init(192);
    SecretKey key = keygen.generateKey();
    System.out.println("Generated Key");
    System.out.println(
    new BigInteger(1, key.getEncoded()).toString(16));
    
    SecureRandom sr = 
    SecureRandom.getInstance("SHA1PRNG");
    byte[] randBytes = new byte[16];
    sr.nextBytes(randBytes);
    IvParameterSpec iv = 
    new IvParameterSpec(randBytes);
    System.out.println("IV");
    System.out.println(
    new BigInteger(1, iv.getIV()).toString(16));
    
    byte[] input = 
    "The big brown fox jumps over a lazy dog."
    .getBytes("UTF-8");
    System.out.println("Plain text");
    System.out.println(new String(input));
    
    Cipher cipher = 
    Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);
    byte[] encryptedData = cipher.doFinal(input);
    System.out.println("Ciphertext");
    System.out.println(
    new BigInteger(1, encryptedData).toString(16));
    
    cipher.init(Cipher.DECRYPT_MODE, key, iv);
    byte[] decryptedData = 
    cipher.doFinal(encryptedData);
    System.out.println("Decrypted Data");
    System.out.println(new String(decryptedData));
  }
}

Result(may vary)
Generated Key
50bfcd0d400bc...
IV
afd7ece30...
Plain Text
The big brown fox jumps over a lazy dog.
Ciphertext
dcae499df38120f91...
Decrypted Data
he big brown fox jumps over a lazy dog.
SecureRandom.getInstance(String algorithm) returns a SecureRandom object that implements the specified Random Number Generator (RNG) algorithm. nextBytes(byte[] bytes) method generates random bytes and put them in the bytes array.

IvParameterSpec specifies an initialization vector (IV). Examples which use IVs are ciphers in feedback mode, e.g., DES in CBC mode and RSA ciphers with OAEP encoding operation. getIV method returns the initialization vector (IV). Take note that The IV size depends on the cryptographic primitive used; for block ciphers it is generally the cipher's block-size. In the example above, the IV size is 16 bytes or 128 bits, which is equal to AES block size.

init(int opmode, Key key, AlgorithmParameterSpec params) Initializes this cipher with a key and a set of algorithm parameters. We use this constructor if the algorithm in our Cipher object requires a parameter. Algorithm-specific parameters in java are represented by AlgorithmParameterSpec and its implementing classes and known subinterfaces.

In the example above, the Cipher object uses CBC(Cipher Block Chaining) and CBC requires an IV(initialization vector) parameter. To fulfill that requirement, we need to instantiate a IvParameterSpec object.

From the documentation:
If the cipher requires any algorithm parameters and params is null, the underlying cipher implementation is supposed to generate the required parameters itself (using provider-specific default or random values) if it is being initialized for encryption or key wrapping, and raise an InvalidAlgorithmParameterException if it is being initialized for decryption or key unwrapping. The generated parameters can be retrieved using getParameters or getIV (if the parameter is an IV).
Take note that we can encrypt files using symmetric encryption. Take a look at this example.
...
byte[] input = java.nio.file.Files.
readAllBytes(
new java.io.File("fileTest.zip").
toPath());

Cipher cipher =
Cipher.getInstance("AES/CBC/PKCS5Padding");

cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encryptedData = cipher.doFinal(input);
System.out.println("File Encrypted!");

cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData =
cipher.doFinal(encryptedData);

try(FileWriter fw =
    new FileWriter("decryptedFile.zip")){
    for(byte b : decryptedData)
      fw.write(b);
}
System.out.println("File Decrypted!");
...
Asymmetric Encryption

Asymmetric Encryption or Public-key cryptography uses a pair of keys to encrypt/decrypt data. In Asymmetric Encryption, there are two types of keys: Public Key and Private Key. Public key is used for encrypting data and private key is used for decrypting data. For example, In a sender-receiver scenario, Assuming we're the receiver, we share our public key to the sender and the sender uses our public key to encrypt the message(data) that the sender wanna send to us.

Then, once we receive the encrypted data, we decrypt it using our private key. Remember that we only share your public keys and we keep our private keys "private". To put it simply, don't share your private key with anyone except you. As you can see, Asymmetric encryption is more secure than symmetric encryption.

Even the attackers intercepted your public key, they couldn't use the public key to decrypt encrypted messages. However, asymmetric encryption is still vulnerable to some kind of attacks. The good news is that those attacks are hard to implement and don't break asymmetric encryption easily. Read this article to know more about the weaknesses of asymmetric encryption.

Another disadvantage of this encryption is that this encryption can encrypt data with limited length. Typically, asymmetric encryption is slower than symmetric encryption. There are different types of algorithms that implement asymmetric encryption. In this tutorial, I'm gonna use the RSA(Rivest–Shamir–Adleman). This article lists algorithms that supports asymmetric encryption.

This example demonstrates asymmetric cryptography using RSA algorithm.
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.PrivateKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;


public class SampleClass{

  public static void main(String[] args) throws
                     BadPaddingException,
                     IllegalBlockSizeException,
                     InvalidKeyException,
                     NoSuchAlgorithmException,
                     NoSuchPaddingException,
                     UnsupportedEncodingException
                     {
    KeyPairGenerator keygen = 
    KeyPairGenerator.getInstance("RSA");
    keygen.initialize(2048);
    
    KeyPair pair = keygen.generateKeyPair();
    PrivateKey privKey = pair.getPrivate();
    PublicKey pubKey = pair.getPublic();
    
    byte[] privKeyBytes = privKey.getEncoded();
    System.out.println("Private Key");
    System.out.println(
    new BigInteger(1, privKeyBytes).toString(16));
    System.out.println("Key Size");
    System.out.println(privKeyBytes.length + " bytes");
    System.out.println(
    (privKeyBytes.length*8) + " bits");
    System.out.println();
    
    byte[] pubKeyBytes = pubKey.getEncoded();
    System.out.println("Public Key");
    System.out.println(
    new BigInteger(1, pubKeyBytes).toString(16));
    System.out.println("Key Size");
    System.out.println(pubKeyBytes.length + " bytes");
    System.out.println(
    (pubKeyBytes.length*8) + " bits");
    System.out.println();
    
    String input = "I love programming!";
    System.out.println("Input");
    System.out.println(input);
    System.out.println();
    
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, pubKey);
    
    byte[] inputBytes = input.getBytes("UTF-8");
    byte[] encryptedData = cipher.doFinal(inputBytes);
    System.out.println("Encrypted Data");
    System.out.println(
    new BigInteger(1, encryptedData).toString(16));
    System.out.println();
    
    cipher.init(Cipher.DECRYPT_MODE, privKey);
    byte[] decryptedData = cipher.doFinal(encryptedData);
    System.out.println("Decrypted Data");
    System.out.println(new String(decryptedData, "UTF-8"));
  }
}

Result(may vary)
Private Key
308204bc020100300d06902a...
Key Size
1216 bytes
9728 bits

Public Key
30820122300d0692a...
Key Size
294 bytes
2352 bits

Input
I love programming!

Encrypted Data
3d6c5729ec927abae5bd...

Decrypted Data
I love programming!
KeyPairGenerator is used to generate pairs of public and private keys. A Key pair generator for a particular algorithm creates a public/private key pair that can be used with this algorithm. It also associates algorithm-specific parameters with each of the generated keys.

RSA is a block cipher and it requires a padding parameter. Fortunately, java automatically provided a padding parameter for RSA when we invoke KeyPairGenerator.getInstance(String algorithm) with "RSA" as our asymmetric encryption algorithm. This article lists asymmetric algorithms that are available to java by default.

initialize(int keysize) Initializes the key pair generator for a certain keysize using a default algorithm-specific parameter set and the SecureRandom implementation of the highest-priority installed provider as the source of randomness. (If none of the installed providers supply an implementation of SecureRandom, a system-provided source of randomness is used.)

RSA supports 512, 1024, 2048 and 4096 bits key size. Key size in this context is not the actual key size of both public and private keys. Key size in this context is used to determine the length(size) of public and private keys. The higher the key size, the longer the length of public and private keys. Keys with higher length are more secure.

However, keys with higher size take time to encrypt/decrypt data. For standard applications, at the time I write this tutorial, 2048 key size is widely used because it has a good balance between security and performance. KeyPair is a simple holder for a key pair (a public key and a private key). It does not enforce any security, and should be treated like a PrivateKey when initialized because the instance of this class holds a private key.

PrivateKey is used to group (and provide type safety for) all private key interfaces. PublicKey is an interface that contains no methods or constants. It merely serves to group (and provide type safety for) all public key interfaces.

generateKeyPair() generates a pair of key(public and private) based on the selected algorithm and key size of KeyPairGenerator instance. getPrivate() retuns a PrivateKey instance with the generated private key stored in KeyPairGenerator instance. getPublic retuns a PublicKey instance with the generated public key stored in KeyPairGenerator instance.

I didn't explain some classes and methods that I've used in the example above because I already explained some of them in previous topics. I assumed you've read previous topics before reading this topic. If you're already familiar with java and its cryptography API, you don't need to read the previous topics.

In the example above, Notice that private key is longer than public key. In RSA, private key stores more information than public key and some of those information are confidential.

Take note that RSA data encryption has length limit. For example, using my java platform's default RSA provider, If I increase the byte length in input variable in the example above:
...
//repeat() means, repeat this string by the
//specified amount in the argument. In this example,
//the String is repeated 50 times.
String input = "I love programming!".repeat(50);
...
I'll get this exception:
...
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Data must not be longer than 245 bytes.
...


The limitation length is based on the key size we put in initialize(int keysize). The limitation length increases/decreasees as key size gets larger/smaller. If we want to encrypt large data, symmetric encrypt is more preferrable to be used. Some of the usage of asymmetric encryption are:
  • Encrypting/Decrypting symmetric keys
  • Encrypting/Decrypting hashes
You may ask yourself on how to verify if a public key that you receive comes from the real sender you're expecting. This is where digital certificate comes in. Digital certificate, to put it simply, is used verify the identity of a public key. Digital certificate is not part of the scope of this tutorial.
Digital Signature

Digital signatures are used for verifying the integrity of data. Digital signature ensures that the data we receive from a sender is not tampered. Digital signature is simply just a hash of the data that is going to be sent and the hash is encrypted. This example demonstrates asymmetric digital signature.
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import javax.crypto.Cipher;

public class SampleClass{

  public static void main(String[] args) throws
                     InvalidKeyException,
                     NoSuchAlgorithmException,
                     SignatureException,
                     UnsupportedEncodingException{
  
    /*Sender*/
    
    KeyPairGenerator keygen = 
    KeyPairGenerator.getInstance("RSA");
    keygen.initialize(2048);
    
    KeyPair pair = keygen.generateKeyPair();
    PrivateKey privKey = pair.getPrivate();
    PublicKey pubKey = pair.getPublic();
    
    String input = "I love programming!";
    
    Signature signatureAlg = 
    Signature.getInstance("SHA256WithRSA");
    signatureAlg.initSign(privKey);
    signatureAlg.update(input.getBytes("UTF-8"));
    byte[] signBytes = signatureAlg.sign();
    
    /*Receiver*/
    
    Signature verificationAlg =
    Signature.getInstance("SHA256WithRSA");
    verificationAlg.initVerify(pubKey);
    verificationAlg.update(input.getBytes("UTF-8"));
    System.out.println("Is data not tampered? " +
    verificationAlg.verify(signBytes));
  }
}

Result
Is data not tampered? true
In the example above, the sender gets the hash of the data and encrypts the hash using his/her private key. Then, the sender sends his/her public key, signature(encrypted hash) and the data to the receiver. Now, the receiver uses the received public key to decrypt the received hash. Then, we get the hash of the received data and compare it to the decrypted hash.

The process that I've explained can be simplified in java. We use Signature to hash and encrypt/decrypt it. Signature.getInstance(String algorithm) returns a Signature object that implements the specified signature algorithm.

"SHA256WithRSA" means SHA-256 is the hash algorithm and RSA is the encryption/decryption algorithm. More available algorithms that are supported by java can be found in this article.

initSign(PrivateKey privateKey) initializes a Signature instance for signing with PrivateKey argument. update(byte[] data) updates the data to be signed or verified, using the specified array of bytes. sign() creates a digital signature with the specified algorithm and data.

In other words, sign() gets the hash of the data, encrypts the hash and return the encrypted hash. initVerify(PublicKey publicKey) initializes a Signature instance for verification with PublicKey argument. verify(byte[] signature) verifies the specified signature.

In other words, verify(byte[] signature) decrypts the encrypted hash (signature), gets the hash of the received data and compare the decrypted hash with the hash of the received data. If both hashes match, the received data is not tampered. In the example above, I only encrypt the hash of the data that is sent to the receiver and not the data itself.

If you only want to check the integrity of your data and now worried about it to be intercepted, You don't need to encrypt the data itself. Otherwise, encrypt the data. I skipped encrypting the data itself in order to focus on digital signature.

Message Authentication Code(MAC)

Message Authentication Code is a symmetric digital signature. MAC uses a single key to create and verify a digital signature. This is also called as keyed hashing because it secures the hash of data by generating the hash with a key. One of the advantages of MAC over asymmetric digital signature is that MAC is much faster than most algorithms used for asymmetric digital signature.

However, asymmetric digital signature is commonly used than MAC because it offers more security. MAC can be cipher-based(CMAC & GMAC) or hash-based(HMAC). In this tutorial, we're going to focus on HMAC. Cipher-based MACs are tricky to implement. It's better to start with HMAC first.

This example demonstrate HMAC.
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;

public class SampleClass{

  public static void main(String[] args) throws
                       NoSuchAlgorithmException,
                       InvalidKeyException,
                       UnsupportedEncodingException{
    
    /*Sender*/
    
    KeyGenerator keygen = KeyGenerator.getInstance("HmacSHA256");
    keygen.init(256);
    SecretKey key = keygen.generateKey();
    
    Mac senderMac = Mac.getInstance("HmacSHA256");
    senderMac.init(key);
    
    String input = "A secret between you and me.";
    senderMac.update(input.getBytes("UTF-8"));
    byte[] senderComputedMac = senderMac.doFinal();
    
    /*Receiver*/
    
    Mac receiverMac = Mac.getInstance("HmacSHA256");
    receiverMac.init(key);
    receiverMac.update(input.getBytes("UTF-8"));
    byte[] receiverComputedMac = receiverMac.doFinal();
    
    String senderMacStr = 
    new BigInteger(1, senderComputedMac).toString(16);
    String receiverMacStr = 
    new BigInteger(1, receiverComputedMac).toString(16);
    
    System.out.println("Sender MAC");
    System.out.println(senderMacStr);
    System.out.println();
    
    System.out.println("Receiver MAC");
    System.out.println(receiverMacStr);
    System.out.println();
    
    System.out.println("Is data not tampered?");
    System.out.println(
    senderMacStr.equals(receiverMacStr));
  }
}

Result

Sender MAC
5df66b8ef0a885bd2...

Receiver MAC
5df66b8ef0a885bd2...

Is data not tampered?
true
In the example above, the sender generates a key, generates MAC by generating a hash from the data with the key and send the key and MAC over secure channel. The data can be sent over unsecure channel if the sender is not worried about the data getting intercepted. Otherwise, encrypt the data first before sending it to the unsecure channel. Next, once the key, MAC and data has been sent to the receiver, the receiver generates a new MAC by generating a hash from the received data with the received key.

If the received MAC and the generated MAC of the receiver are the same, it means that the data came from the sender who owns the received key and it's not tampered. Otherwise, the data may be tampered, the sender gave a wrong key or the received key is not the key that generated the received MAC.

Key size is 256 bits because at the time I write this, 256 bits keysize should suffice for standard applications. If you want more security at the expense of performance, you can increase the key size. The algorithm used is "HmacSHA256" or generated HMAC using SHA-256 algorithm. More supported Mac algorithm by java can be found in this article.

The algorithm used in the KeyGenerator and Mac instances must be the same. Take note that HmacMD5 and HmacSHA1 are discouraged to be used because they're not secure anymore. Use other algorithms like HmacSHA2 such as HmacSHA256 and HmacSHA512; HmacSHA3 algorithms.

If you want more security at the expense of performance, use hash algorithm with higher block size like HmacSHA512 or use the new SHA family which is the SHA3 family. SHA2 and SHA3 have different algorithms. SHA2 is vulnerable to length extension attack whereas SHA3 is not.

Mac class provides the functionality of a "Message Authentication Code" (MAC) algorithm.

Key Wrapping

Key wrapping is a process of encrypting a key using another key. This example demonstrates wrapping/unwrapping symmetric key using asymmetric keys.
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.*;
import javax.crypto.*;

public class SampleClass{

  public static void main(String[] args) throws
                     BadPaddingException,
                     IllegalBlockSizeException,
                     InvalidKeyException,
                     NoSuchAlgorithmException,
                     NoSuchPaddingException,
                     UnsupportedEncodingException{
    KeyPairGenerator keyPairGen = 
    KeyPairGenerator.getInstance("RSA");
    keyPairGen.initialize(2048);
    
    KeyPair pair = keyPairGen.generateKeyPair();
    PrivateKey privKey = pair.getPrivate();
    PublicKey pubKey = pair.getPublic();
    
    KeyGenerator keygen =
    KeyGenerator.getInstance("AES");
    keygen.init(192);
    
    SecretKey secretKey = keygen.generateKey();
    
    String input = "Send this message!";
    System.out.println("input: " + input);
    
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    
    byte[] encryptedData = 
    cipher.doFinal(input.getBytes("UTF-8"));
    System.out.println("Encrypted Data");
    System.out.println(
    new BigInteger(1, encryptedData).toString(16));
    
    cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.WRAP_MODE, pubKey);
    byte[] encryptedKey = 
    cipher.wrap(secretKey);
    
    cipher.init(Cipher.UNWRAP_MODE, privKey);
    SecretKey decryptedKey = 
    (SecretKey)cipher.unwrap(encryptedKey, "AES", 
    Cipher.SECRET_KEY);
    
    cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, decryptedKey);
    byte[] decryptedData = cipher.doFinal(encryptedData);
    
    System.out.println("Decrypted Data: " + 
    new String(decryptedData, "UTF-8"));
  }
}

Result
input: Send this message!
Encrypted Data
118729c524039308c...
Decrypted Data: Send this message!
In the example above, The sender wraps the data that he is going to send using AES which is a symmetric encryption algorithm. Then, the generated key for the encrypted data is encrypted using RSA algorithm which is an asymmetric encryption algorithm. He uses the public key of the receiver to encrypt the key of the encrypted data.

The sender sends the encrypted key and data to the receiver. Then, the receiver unwraps(decrypts) the wrapped(encrypted) key using his private key. Then, he uses the unwrapped key to decrypt the data. We can use the example above if we want to securely transfer a large encrypted data to a receiver. The asymmetric keys encrypt/decrypt the symmetric key of the large data and the symmetric key encrypts the large data.

To wrap a key, invoke init(int opmode, Key key) method of Cipher class. opmode parameter is the operation mode. We put Cipher.WRAP_MODE if we're going to wrap a key. key parameter is the key that we're going to use to wrap a key. Once init is invoked. Invoke wrap(Key key) of Cipher class to wrap the key. key parameter is the key that we're going to wrap.

Next, to unwrap the key, we invoke init again but this time the operation mode is Cipher.UNWRAP_MODE and the key is the private key of the receiver. Once init is invoked, invoke unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType) method.

This method unwraps a wrapped key. wrappedKey parameter is the bytes of the encrypted key. wrappedKeyAlgorithm is the associated algorithm of the encrypted key. wrappedKeyType is the key type of the encrypted key. There are three types of keys in Cipher class:
Cipher.PRIVATE_KEY
Cipher.PUBLIC_KEY
These keys are keys for asymmetric encryption.
Cipher.SECRET_KEY
This key is a key for symmetric encryption.

Storing Keys

There are two ways of storing keys. The simplest way of storing keys is to write keys to a persistent file. Make sure that you store your keys in a safe place and don't share your key with untrusted people.

Write the key hash of your key to a persistent file if you're planning to use your keys to other programming platforms. If you're planning to use your keys only in java, you can serialize the keys. SecretKey, PublicKey and PrivateKey are serializable.

This example demonstrates storing key hash in a file and recreating the key in java.
import java.io.*;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class SampleClass{

  public static void main(String[] args) throws
                     IOException,
                     NoSuchAlgorithmException{
    File secretKeyFile = 
    new File("key/my-secret.key");
    
    KeyGenerator keygen =
    KeyGenerator.getInstance("AES");
    keygen.init(192);
    
    SecretKey secretKey = keygen.generateKey();
    
    //write the keys to a persistent file
    if(!secretKeyFile.exists()){
         try(BufferedWriter bw = 
             new BufferedWriter(
             new FileWriter(secretKeyFile))){
             String key =  
             new BigInteger(1, secretKey.getEncoded())
             .toString(16);
             bw.write(key, 0, key.length());
             System.out.println("Key written to file");
             System.out.println(key);
             
             //let gc remove the key as soon as possible
             //After use, don't let the key persist with
             //memory for too long.
             key = null;
         }
     }
     else{
       System.out.println("File/s already exist!");
       return;
     }
    
    //read the keys from the persistent file
    try(FileReader fr = new FileReader(secretKeyFile)){
      StringBuilder sb = new StringBuilder();
      
      int i = 0;
      while((i = fr.read()) != -1)
        sb.append((char)i);
      
      //convert the key hash to array of bytes
      //in order to recreate the secret key using
      //SecretKeySpec class
      SecretKey keyFromFile = 
      new SecretKeySpec(
      HexFormat.of().parseHex(sb.toString()),
      "AES");
      sb = null;
      
      System.out.println("key read from file");
      System.out.println(
      new BigInteger(1, 
      keyFromFile.getEncoded()).toString(16));
    }
    
  }
}

Result
Key written to file
73f6040f39f1...
Key read from file
73f6040f39f1...
HexFormat is a utility class that we can use to parse hex strings. In the example above, we're writing a plain key hash in a persistent file. If you want to obscure key hash, you can use Base64 in java.

HexFormat is introduced in java 17. If you're using java with version lower than 17, HexFormat is not available. This dicussion has a solution for parsing key hashes that works in java with version lower than 17.

If you're planning to use your keys on other platforms and you want to obscure them using Base64, make sure that the platform can decode Base64 characters. SecretKeySpec can be used to construct a SecretKey from a byte array, without having to go through a (provider-based) SecretKeyFactory.

SecretKeySpec is only useful for raw secret keys that can be represented as a byte array and have no key parameters associated with them, e.g., DES or Triple DES keys.

Another way of storing keys is to create a KeyStore. KeyStore can store keys and certificates. In this tutorial, I'm gonna demonstrates on how to store a key for symmetric encryption.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.File;
import java.math.BigInteger;
import java.security.cert.CertificateException;
import java.security.NoSuchAlgorithmException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.KeyStore.PasswordProtection;
import java.security.UnrecoverableKeyException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

public class SampleClass{

  public static void main(String[] args) throws
                          IOException,
                          NoSuchAlgorithmException,
                          CertificateException,
                          KeyStoreException,
                          UnrecoverableKeyException{
    
    KeyGenerator keygen =
    KeyGenerator.getInstance("AES");
    keygen.init(192);
    
    SecretKey secretKey = keygen.generateKey();
    
    KeyStore keystore = KeyStore.getInstance("JCEKS");
    //This is unsecure. Don't hardcode passwords.
    //It's better to put your KeyStore password
    //in a persistent file. Before that, obscure
    //or encrypt your password first.
    char[] pass = "password".toCharArray();
    
    //initialize Keystore
    keystore.load(null, pass);
    
    File source = new File("keystore/my-keystore.jks");
    if(source.exists()){
       System.out.println("KeyStore already exists!");
       return;
    }
    
    //write KeyStore object to a file
    try(FileOutputStream fos = new 
        FileOutputStream(source)){
        keystore.store(fos, pass);
    }
    
    //Initialize KeyStore from the given
    //input stream
    try(FileInputStream fis = new 
        FileInputStream(source)){
        keystore.load(fis, pass);
    }
    
    //add entry to KeyStore
    KeyStore.SecretKeyEntry keyEntry =
    new KeyStore.SecretKeyEntry(secretKey);
    KeyStore.ProtectionParameter entryPass =
    new KeyStore.PasswordProtection(pass);
    keystore.setEntry("secret-key", keyEntry, entryPass);
    
    SecretKey ksSecretKey = 
    (SecretKey)keystore.getKey("secret-key", pass);
    
    System.out.println("Generated Key");
    System.out.println(
    new BigInteger(1, secretKey.getEncoded()).toString(16));
    System.out.println();
    
    System.out.println("Key in KeyStore");
    System.out.println(
    new BigInteger(1, ksSecretKey.getEncoded()).toString(16));
  }
}

Result
Generated Key
e71094e7294563d771...

Key in KeyStore
e71094e7294563d771...
getInstance(String type) returns KeyStore object with the specified type. JCEKS is similar to JKS(Java KeyStore). Although, JCEKS supports symmetric keys whereas JKS only supports asymmetric keys. In terms of security, JCEKS has stronger key protection.

load(InputStream stream, char[] password) Loads the KeyStore contents from the given input stream to the KeyStore intance. If stream argument is null, this method initializes KeyStore with no contents and associates the given password to it. store(OutputStream stream, char[] password) writes the KeyStore object to a persistent file via output stream and protect the file's integrity with the given password.

KeyStore.SecretKeyEntry creates an entry for a secret key.KeyStore.ProtectionParameter is used to protect the contents of a key store. KeyStore.PasswordProtection is used to wrap password that can be implemented in key store.

setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam) saves a KeyStore entry to a KeyStore. alias parameter is the name associated with the entry. We can use alias if we want to select the entry associated with the alias. entry parameter is the entry that we wanna store in the KeyStore. protParam parameter is a protection of KeyStore wrapped into KeyStore.ProtectionParameter.

getKey(String alias, char[] password) retrives a key with the associated alias and the password that we put in the entry of the key. Storing symmetric key in KeyStore is simple. However, Some keys or certificates are kinda tricky to be stored in KeyStore. Another way of create a KeyStore is using the keytool command in java. keytool is not in the scope of this tutorial.

Salted Password

Salted password is a password that is mixed with cryptographic salt. Cryptographic salt or salt for short, is random data that is used as an additional input to a one-way function that hashes data. In other words, we mix salt and a data, password in our case, and then generate a key from the password with mixed salt.

In this tutorial we're gonna be using PBKDF2. bcrypt and scrypt are some of the popular alternatives to PBKDF2 but java doesn't support those algorithm by default. If you wanna use those algorithms, you need a third-party vendor that implements those algorithms.

This example demonstrates PBKDF2.
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

public class SampleClass{
  public static void main(String[] args)
                throws UnsupportedEncodingException,
                       NoSuchAlgorithmException,
                       InvalidKeySpecException{
    /*Encrypting*/
    String pass = "password";
    byte[] salt = new byte[16];
    SecureRandom sr =
    SecureRandom.getInstance("SHA1PRNG");
    sr.nextBytes(salt);
    int iterations = 10000;
    int keyLength = 512;
    
    SecretKeyFactory keyFactory =
    SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    
    PBEKeySpec spec =
    new PBEKeySpec(pass.toCharArray(), salt,
                   iterations, keyLength);
    SecretKey key = keyFactory.generateSecret(spec);
    byte[] generatedBytes = key.getEncoded();
    String storedHash = 
    new BigInteger(1, generatedBytes).toString(16);
    System.out.println("Stored Hash");
    System.out.println(storedHash);
    System.out.println();
    
    /*Verification*/
    keyFactory = 
    SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    spec = new PBEKeySpec(pass.toCharArray(), salt,
                          iterations, keyLength);
    key = keyFactory.generateSecret(spec);
    generatedBytes = key.getEncoded();
    String inputHash =
    new BigInteger(1, generatedBytes).toString(16);
    System.out.println("Input Hash");
    System.out.println(inputHash);
    System.out.println();
    
    System.out.println("Stored and input hashes equal?");
    System.out.println(storedHash.equals(inputHash));
  }
}

Result
Stored Hash
b2f36d5f92ac1f...

Input Hash
b2f36d5f92ac1f...

Stored and input hashes equal?
true
In the example above, SecureRandom is instantiated with "SHA1PRNG" algorithm. There are other algorithms that are available and they can be found here. "SHA1PRNG" is commonly used PRNG(Pseudo-Random Number Generator) algorithm due to simplicity of SHA-1 algorithm.

When generating hash or cryptographic key, SHA-1 is discouraged to be used because it's unsecure. Since we just want to generate random numbers that can be mixed with our password, "SHA1PRNG" is alright to be used. Next, we set iterations variable to 10,000 and keyLength variable to 512.

PBEKeySpec class can create a key specification with the specification of password-based encryption(PBE). PBKDF2 is a password-based encryption. Thus, The key that is created by PBEKeySpec is compatible with PBKDF2.

PBEKeySpec(char[] password, byte[] salt, int iterationCount, int keyLength) generates a key spec with the specified password mixed with salt, iteration and key length. password parameter is the password that is gonna be mixed with salt. salt parameter is the random bytes that is gonna be mixed with password. Standard salt size for PBKDF2 is at least 64 bits or 8 bytes.

In the example above, I use 16 bytes or 128 bits recommended by The US National Institute of Standards and Technology. Salt is a way to reduce the ability of attackers to break a password using pre-computed hashes such as rainbow tables.

IterationCount parameter is the number of repetitive use of pseudorandom for password along with the salt. This process makes password cracking much more difficult. This process is also known as key stretching.

Organizations have different standards when it comes to the number of iteration count. For example, Apple reportedly used 2000 for iOS 3, and 10000 for iOS 4. OWASP recommended to use 310000 iterations for PBKDF2-HMAC-SHA256 and 120000 for PBKDF2-HMAC-SHA512.

If you want more security from password cracking, you can increase the itearation count anytime. Just remember that the higher the iteration count, the slower the key generation. If computers get faster in the future, attackers can crack passwords fast. To counter that problem, we can just increase the iteration count.

keyLength is the length of the key. Long keys are harder to break. Thus, adding more security to our passwords. For stanrdard applications, length of 512 bits should suffice.

Once we generate a key spec for PBKDF2. We need to convert the key spec into a cryptographic key. To do that, we need to get an instance of SecretKeyFactory. Key factories are used to convert keys (opaque cryptographic keys of type Key) into key specifications (transparent representations of the underlying key material), and vice versa.

SecretKeyFactory.getInstance(String algorithm) returns a SecretKeyFactory object that converts secret keys of the specified algorithm. Available algorithms for SecretKeyFactory provided by java can be found here.

In the example above, I use "PBKDF2WithHmacSHA512" algorithm. This algorithm uses PBKDF2 with HMAC with SHA512. generateSecret(KeySpec keySpec) generates a SecretKey object from the provided key specification. We use SecretKey as a key type of PBKDF2 because PBKDF2 with HMAC is a symmetric encryption.

Once we successfully generated a SecretKey for our password with salt, we need to get its hash in hexadecimal form. To do that, we need to get its byte structure by invoking getEncoded() method and convert the bytes to hexadecimal using BigInteger.

We can use formatHex(byte[] bytes) in HexFormat class to convert array of bytes to hex. However, HexFormat is introduced in java 17. Java versions older than 17 can't use HexFormat.

Now, We need to store the hash of the key in hexadecimal form, salt, iteration count and length of the key because during verification, we need those values. Now, assume that the user is gonna login to our program.

To verify that the password he/she sends to our program is the same as the password he/she sent to our program, we need to create a key with the stored salt, iteration count and key length. Then, get the hash of the new key and compare the new hash to the stored hash. If they're both the same then the user can access his/her account.

Saturday, April 23, 2022

Java Tutorial: Modules

Chapters

Java Modules

In java 9, java introduces Java modules. Java modules or Java platform module system (JPMS) is akin to Java packages but it's much secure than Java packages. A module is used to manage packages and communicate with another module. These are the benefits that we can get from modules:
  • Freedom to choose which packages to be distributed.
  • Strong encapsulation.
  • Missing classes can be detected early at startup.
These are the cons of modules:
  • No mutual dependency. It means that if a module requires the other module, the other module can't require the module back.
  • Split package not allowed. A split package happens if two modules export the same package, splitting the content of the package between modules.
  • Increased complexity. Unlike creating java packages, creating modules and linking them are more complex.
Java modules are divided into different types:

System Modules. These modules are provided by java. In java 9, java changes its package structure and moves predefined packages in modules. We can check this modules out by typing this command on your terminal or cmd(Windows): java --list-modules

Application Modules. These are user-made modules.

Automatic Modules. These modules contain Unofficial modules content of existing JAR files that don't have module-info.class. module-info.class holds information about how modules operate. This module will be automatically created if an existing JAR file without module-info.class is included in a module path.

Its name will be derived from the name of the JAR file. This module will be granted full read access to every module loaded by the module path. This module is useful when migrating old existing JAR files to modularity.

Unnamed Modules. These are modules that contain JAR files content loaded in classpath instead of module path. This module is useful for maintaining backward compatibility with written code in java 8 and below.

Modules can be distributed via JAR files or "exploded" directory tree corresponding to a package hierarchy. Now, let's discuss how to create a module. First off, we need a module descriptor. This descriptor is a java file named as module-info.java. Module descriptor holds information about how a module operates.

This is the structure of a module descriptor:
module module-name{
  //Directives...
}
module is a keyword, module-name is the name of a module block. Directives are statements that define how packages are used. Module descriptor must be placed at the root of packages. In the tutorial, we're gonna create modules using a terminal or command prompt on windows. For production, it's better to use IDEs to make things more easier.

Take note that this is an introductory tutorial. More information can be found in this article.

exports and requires Directives

exports and requires Keywords are keywords used in directive statements. By default, all packages in a module can't be accessed by other modules. Exports directive allows modules to access named package's public classes and their members and exported packages. Take note that export directive doesn't allow other modules to access non-public members of a class in the named package via reflection, even we invoke setAccessible method of java reflection.
Here's the syntax for exports directive: exports package-name;

requires keyword adds a dependency to a module. requires keyword will look for the named module of requires directive in module graph. Java compiler will complain if the named module can't be found.
Here's the syntax for requires directive: requires module-name;

Now, let's test these directives. First off, create this file structure:
|company.modules
  |resources
    |Consumer.java
  |module-info.java
|main.app
  |main
    |Main.java
  |module-info.java
company.modules and main.app are module packages. As you can see, each module has module-info.java. Also, if a module package has two or more words, It's recommended to separate them with dots(.) not underscores(_). resources and main are regular packages and they contain classes. Next, write this code to module-info.java in company.modules directory:
module company.modules{
  exports resources;
}
Write this code to module-info.java in main.app directory:
module main.app{
  requires company.modules;
}
As you can see, module names are equivalent to module packages. Write this code to Consumer.java:
package resources;

public class Consumer{
  public Consumer(){
    System.out.println("Consumer Created!");
  }
}
Write this code to Main.java:
package main;

import resources.Consumer;

public class Main{
	
	public static void main(String[] args){
		Consumer consumer = new Consumer();
	}
}
Now, let's start creating the modules. First off, let's build company.modules. Open your terminal(or cmd if you're using windows) in the directory where the module packages reside and type this command to compile Consumer.java:

javac -d output company.modules\resources\Consumer.java

After that, type this command to compile module-info.java in company.modules directory:

javac -d output company.modules\module-info.java

-d means destination directory. output is the destination directory, java will create one if the directory doesn't exist.

Next, let's put the compiled file to a JAR file. Type this command to create a JAR file and put the compiled files above in the JAR:

jar -c -f build\company.modules.jar -C output .

-c means create a JAR. -f means the location of the JAR file that is going to be created. Java will create necessary directories if directories in the location path don't exist. -C means the location of the content that is going to be put in the JAR file. output is the source location and . means include files starting from the root of output. Take note that a JAR file is limited to one module.

Next, let's build main.app module. First off, type this commands to compile module-info.java in main.app and Main.java:

javac --module-path build -d output2 main.app\module-info.java
javac --module-path build -d output2 main.app\main\Main.java

--module-path in javac command refers to a path where modules are located. company.modules is in build directory and since that module is an application module, we need to explicitly tell to the compiler where company.modules is located in order to compile the files. Otherwise, the compiler will complain.

Next, it's time to create a JAR file and put the compiled files in it. Type this command to create a JAR file:

jar -c -f build\main.app.jar -e main.Main -C output2 .

-e means location of a class with main method or the entry point. In the example above, Main.java is the entry point and it's located at main package.

Next, let's run our program. To run the program. Type this command:

java --module-path build -m main.app

--module-path in java command refers to a path where the module we wanna run is located. -m refers to the module with main class or entry point. If you successfully ran the program, the result is going to be:

Consumer Created!

You may repeat the steps if you failed to run the program. More details about java commands can be found in this article.

requires keyword has two additional variants:

requires static
requires transitive

requires static adds an optional dependency to a module. For example, take a look at this module descriptor:

module main.app{ requires work.modules; requires static optional.modules; }

During compilation, optional.modules will be checked. During runtime, optional.modules won't be checked. However, if optional.modules becomes present in the module graph(e.g. added manually with --add-modules), modules that have optional dependency to optional.modules can read the module.

One example where requires static can be used is when we set a maven dependency to 'compile' or 'provided' scope. For example, Lombok library is usually provided by a server. Thus, this dependency's scope is usually set to 'provided'. 'provided' scope excludes the dependency in our built project because it's expected to be provided by other sources such as servers.

requires transitive allows a module to read a module that's required by another module. This is called implied readability. For example:

module work.modules{
  exports resources;
}

module company.modules{
  exports inventory;
  requires transitive work.modules;
}

module main.app{
  requires company.modules;
}
In the example above, main.app can read work.modules without explicit requires directive coming from main.app.

exports keyword also has a variant. exports... to is more restrictive than exports. exports... to only allows packages to be exported to selected modules separated with comma(,). For example:
module work.modules{
  exports resources to company.modules, main.app;
}

uses and provides... with Directives

uses and provides... with directives can be used for creating services and service providers. In java, a service a service is a well-known interface or class for which zero, one, or many service providers exist. A service provider (or just provider) is a class that implements or subclasses the well-known interface or class.

Take note that interface or abstract class of a service is used in uses directive not the implementation. Let's create a service. first off, create this file structure:
|company.modules
  |resources
    |ButtonInterface.java
    |ButtonFactory.java
  |module-info.java
|main.app
  |main
    |Main.java
  |module-info.java
After that, create an interface.
package resources;

public interface ButtonInterface{
  void create(String button);
}
Next, create a concrete class that implements ButtonInterface.
package resources;

public class ButtonFactory implements ButtonInterface{
  
  @Override
  public void create(String button){
    System.out.println(button + " created!");
  }
}
In the main method, we can use the service via ServiceLoader.
package main;

import java.util.ServiceLoader;
import resources.ButtonInterface;

public class Main{
  
  public static void main(String[] args){
    ButtonInterface btn = 
    ServiceLoader.load(ButtonInterface.class).
    iterator().next();
    btn.create("Sample Button");
  }
}
Now, go to module-info.java in company.modules and type these directives:

exports resources;
provides resources.ButtonInterface with resources.ButtonFactory;


company.modules is providing the service we created. Thus, it's the service provider. Next, go to module-info.java in company.modules in main.app and type these directives:

requires company.modules;
uses resources.ButtonInterface;

uses directive is in main.app is the user of service. Next, compile the classes in resources package and also the module-info.java in company.modules.
javac -d output company.modules\resources\ButtonInterface.java
javac -d output -cp output company.modules\resources\ButtonFactory.java
jar -c -f build\company.modules.jar -C output .
I added -cp because ButtonFactory.java would look for .class file of ButtonInterface.java. Next, let's compile the classes in main.app package and also the module-info.java in main.app.
javac -d output2 --module-path build main.app\module-info.java
javac -d output2 --module-path build main.app\main\Main.java
jar -c -f build\main.app.jar -e main.Main -C output2 .
Then, run Main.java:

java --module-path build -m main.app

The result is going to be: Sample Button Created!

opens Directive and open Modifier

opens directive and open modifier allow non-public members of a class in a package to be accessed via reflection. open is a modifier that we can add before the module name. For example:
open module my.module{
  exports package1;
  exports package2;
  exports package3;
}
All non-public members of classes in exported packages of my.module can be accessed via reflection. If we don't want our module to be fully exposed, we can use the opens directive. For example:
module my.module{
  exports package1;
  exports package2;
  exports package3;
  
  opens package1;
}
In the example above, only the non-public members of classes of package1 are accessible via reflection. If we want to select which modules can access non-public members of classes of a package via reflection, we can use opens... to directive. For example:
module my.module{
  exports package1;
  opens package1 to module1, module2;
}