JavaRush /Java Blog /Random EN /Java Cryptography Architecture: First introduction
Viacheslav
Level 3

Java Cryptography Architecture: First introduction

Published in the Random EN group
Security of data exchange is one of the most important properties of modern applications. Since ancient times, people have come up with cunning methods, which with the development of mankind became the whole science of Cryptography. Naturally, Java did not stand aside and offered developers the Java Cryptography Architecture (JCA). This review should give a first idea of ​​how it works.

Preface

I propose to travel back in time. Before us is Ancient Rome. And before us is Gaius Julius Caesar, who sends a message to his commanders. Let's see what's in this message:
Java Cryptography Architecture: First introduction - 2
What could this mean: "ЕСКЕУГЬГМХИФЯ Е УЛП"? Let's open Java Online Compiler, for example: repl.it
class Main {
  public static void main(String[] args) {
    String code = "ЕСКЕУГЬГМХИФЯ Е УЛП";
    for (char symbol : code.toCharArray()) {
      if (symbol != ' ') {
        symbol = (char) (symbol - 3);
      }
      System.out.print(symbol);
    }
  }
}
Before us is the simplest implementation of the Caesar Cipher. According to the work of the ancient Roman historian Suetonius entitled “The Lives of the Twelve Caesars,” this is exactly how Caesar encrypted messages to his generals. And this is one of the most ancient references to the use of such a thing as Cryptography . The word "cryptography" comes from the ancient Greek words "hidden" and "write", i.e. it is the science of privacy techniques. Java has its own support for cryptography and it is called Java Cryptography Architecture (JCA). The description can be found in the official documentation from Oracle - " Java Cryptography Architecture (JCA) ". I suggest you look at what opportunities we get thanks to JCA.
Java Cryptography Architecture: First introduction - 3

J.C.A.

As we previously learned, Java offers the Java Cryptography Architecture (JCA) for working with cryptography. This architecture contains an API (i.e. a certain set of interfaces) and providers (that implement them):
Java Cryptography Architecture: First introduction - 4
As the documentation says, " The Java platform includes a number of built-in providers ". That is, the Java platform provides a set of built-in providers that can be expanded if necessary. You can see this for yourself:
import java.security.Provider;
import java.security.Security;
class Main {
  public static void main(String[] args) {
    Provider[] providers = Security.getProviders();
    for (Provider p : providers) {
      System.out.println(p.getName());
    }
  }
}
Registering a third party provider is very easy. For example: Security.addProvider(new BouncyCastleProvider()); This example connects one of the most famous providers - BouncyCastle . But in this review we will use only basic tools, without third-party libraries. Our main document: " Java Cryptography Architecture (JCA) ". Understanding how JCA works will help you more easily understand the technologies in which this same JCA is actively used. For example: HTTPS (see " From HTTP to HTTPS ").
Java Cryptography Architecture: First introduction - 5

MessageDigest

The first thing mentioned in the JCA documentation is MessageDigest. In general, Digest in Russian will be the same - a digest corresponds in meaning to “a summary”. But in cryptography, a digest is a hash sum. You can also easily remember that in English Digest can also be translated as digest. More details can be found in the JCA documentation in the " MessageDigest " section. As the documentation says, MessageDigest generates a fixed-size result called digest or hash. Hashing is a one-way function, i.e. if we hashed something, then from the result (i.e. from the hash) we cannot get the original source. But if identical objects are hashed (for example, strings of identical characters), then their hash must match. As stated in the documentation, such a hash is sometimes also called a “checksum” or “digital fingerprint” of data. Hashing can be performed using different algorithms. Available algorithms can be viewed in the document " Java Cryptography Architecture Standard Algorithm Name Documentation for JDK 8 ". Let's do the hashing and print the hash to the console:
import javax.xml.bind.DatatypeConverter;
import java.security.*;
public class Main {
  public static void main(String[] args) {
    try {
      MessageDigest digester = MessageDigest.getInstance("SHA-512");
      byte[] input = "Secret string".getBytes();
      byte[] digest = digester.digest(input);
      System.out.println(DatatypeConverter.printHexBinary(digest));
    } catch (NoSuchAlgorithmException e) {
      throw new IllegalStateException(e);
    }
  }
}
Hashing can be useful, for example, when storing passwords. Since the hash of the entered password can be checked against a previously saved hash. If the hashes match, then the password also matches. For even more secure hashing, a concept called “salt” is used. Salt can be implemented using the SecureRandom class . Before executing the digest method, let's describe adding "salt":
byte[] salt = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(salt);
digester.update(salt);
But hash is a one-way function. But what if you want to be able to encrypt and decrypt?
Java Cryptography Architecture: First introduction - 6

Symmetric key cryptography

Symmetric encryption is encryption that uses the same key for encryption and decryption. In order to use symmetric encryption we need a key. To get it we use KeyGenerator . In addition, we will need a class that represents a cipher ( Cipher ). As stated in the JCA documentation in the section “ Creating a Cipher Object ”, to create a Cipher you need to specify not just an algorithm, but a “transformation” in the line. The transformation description looks like this: "algorithm/mode/padding":
  • Algorithm : here we look at the standard names for “ Cipher (Encryption) Algorithms ”. It is recommended to use AES.
  • Mode : encryption mode. For example: ECB or CBC (we'll talk about this a little later)
  • Indentation/Split : Each block of data is encrypted separately. This parameter determines how much data is counted as 1 block.
For example, take the following transformation: "AES/ECB/PKCS5Padding". That is, the encryption algorithm is AES, the encryption mode is ECB (short for Electronic Codebook), the block size is PKCS5Padding. PKCS5Padding says that the size of one block is 2 bytes (16 bits). The Electronic Codebook encryption mode involves sequential encryption of each block:
Java Cryptography Architecture: First introduction - 7
It might look like this in code:
import javax.xml.bind.DatatypeConverter;
import javax.crypto.*;
import java.security.Key;
public class Main {
  public static void main(String[] args) throws Exception {
    String text = "secret!!secret!!secret!!secret!!";
    // Generate new key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    keygen.init(256);
    Key key = keygen.generateKey();
    // Encrypt with key
    String transformation = "AES/ECB/PKCS5Padding";
    Cipher cipher = Cipher.getInstance(transformation);
    cipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] encrypted = cipher.doFinal(text.getBytes());
    System.out.println(DatatypeConverter.printHexBinary(encrypted));
    // Decrypt with key
    cipher.init(Cipher.DECRYPT_MODE, key);
    String result = new String(cipher.doFinal(encrypted));
    System.out.println(result);
  }
}
If we execute, we will expect to see a repeat, because we specified 32 characters. These characters make up 2 blocks of 16 bits:
Java Cryptography Architecture: First introduction - 8
To avoid replay in this case, you should use another mode - Cipher Block Chaining (CBC). This mode introduces the concept of Initialization Vector (represented by the IvParameterSpec class). And also thanks to this mode, the result of generating the last block will be used to generate the next one:
Java Cryptography Architecture: First introduction - 9
Let's now write this in code:
import javax.xml.bind.DatatypeConverter;
import javax.crypto.*;
import java.security.*;
import javax.crypto.spec.IvParameterSpec;
public class Main {
  public static void main(String[] args) throws Exception {
    // Initialization Vector
    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] rnd = new byte[16];
    random.nextBytes(rnd);
    IvParameterSpec ivSpec = new IvParameterSpec(rnd);
    // Prepare key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    keygen.init(256);
    Key key = keygen.generateKey();
    // CBC
    String text = "secret!!secret!!secret!!secret!!";
    String transformation = "AES/CBC/PKCS5Padding";
    Cipher cipher = Cipher.getInstance(transformation);
    cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
    byte[] enc = cipher.doFinal(text.getBytes());
    System.out.println(DatatypeConverter.printHexBinary(enc));
    // Decrypt
    cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
    String result = new String(cipher.doFinal(enc));
    System.out.println(result);
  }
}
As we see, as a result we do not see repeated cipher blocks. For this reason, ECB mode is not recommended, because makes it possible to see repetitions and use this knowledge for decryption. For more information about ECB and CBC, I advise you to read the material: “ Electronic codebook mode ”. But symmetric encryption has an obvious problem - you need to somehow transfer the key from the one who encrypts to the one who encrypts. And along this path, this key can be intercepted and then it will be possible to intercept data. And asymmetric encryption is designed to solve this problem.
Java Cryptography Architecture: First introduction - 10

Asymmetric encryption

Asymmetric encryption or Public-key cryptography is an encryption method that uses a pair of keys: a private key (kept secret from everyone) and a public key (available to the public). This separation is necessary in order to securely exchange the public key between the parties to the exchange of information, while keeping the secret key safe. When creating a key pair, KeyGenerator is no longer enough for us; we need KeyPairGenerator . Let's look at an example:
import javax.crypto.*;
import java.security.*;
public class Main {
  public static void main(String[] args) throws Exception {
    KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
    generator.initialize(1024);
    KeyPair keyPair = generator.generateKeyPair();
    // Encrypt with PRIVATE KEY
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
    byte[] data = cipher.doFinal("Hello!".getBytes());
    // Decrypt with PUBLIC KEY
    cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
    byte[] result = cipher.doFinal(data);
    System.out.println(new String(result));
  }
}
It is important to understand here that when using asymmetric encryption, we always use KeyPair to use one key for encryption and another for decryption. But because The point of encryption is that only the recipient can decrypt it; it is encrypted with a public key, and decrypted only with a private one.
Java Cryptography Architecture: First acquaintance - 11

Digital signature

As we saw above, knowing the public key, you can send data so that only the owner of the private key can decrypt it. That is, the essence of asymmetric encryption is that anyone encrypts, but only we read. There is also a reverse procedure - a digital signature, represented by the Signature class . A digital signature can use the following algorithms: " Signature Algorithms ". The JCA documentation suggests taking a closer look at these two: DSAwithMD5 and RSAwithMD5 What is better than DSA or RSA and what is their difference you can read here: " Which Works Best for Encrypted File Transfers - RSA or DSA? ". Or read the discussions here: " RSA vs. DSA for SSH authentication keys ". So, digital signature. We will need, as before, a KeyPair and a new Signature class. If you have so far tested in online compilers, then the following example may be somewhat difficult for them. My example only ran here: rextester.com . We import the classes we need:
import javax.crypto.*;
import java.security.*;
We’ll also rewrite the main method:
public static void main(String[] args) throws Exception {
    // Generate keys
    KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
    SecureRandom random = SecureRandom.getInstanceStrong();
    generator.initialize(2048, random);
    KeyPair keyPair = generator.generateKeyPair();
    // Digital Signature
    Signature dsa = Signature.getInstance("SHA256withRSA");
    dsa.initSign(keyPair.getPrivate());
    // Update and sign the data
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
    byte[] data = cipher.doFinal("Hello!".getBytes());
    dsa.update(data);
    byte[] signature = dsa.sign();
    // Verify signature
    dsa.initVerify(keyPair.getPublic());
    dsa.update(data);
    boolean verifies = dsa.verify(signature);
    System.out.println("Signature is ok: " + verifies);
    // Decrypt if signature is correct
    if (verifies) {
      cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
      byte[] result = cipher.doFinal(data);
      System.out.println(new String(result));
    }
}
This is how a digital signature works. Digital signature is an interesting topic. I advise you to look at the report on this topic:
Java Cryptography Architecture: First acquaintance - 12
Above we saw how the parties exchange data. Isn't there some standard interface for this interaction provided in JCA? It turns out that there is. Let's look at it.
Java Cryptography Architecture: First acquaintance - 13

KeyAgreement

The Java Cryptography Architecture introduces an important tool - Key agreement is a protocol. It is represented by the KeyAgreement class . As stated in the JCA documentation, this protocol allows you to set the same cryptographic key for multiple parties, while no secret information is transferred between the parties. Sounds weird? Then let's look at an example:
// 1. Одна из сторон (Алиса) генерирует пару ключей. Encoded публичный ключ отдаёт.
KeyPairGenerator generator = KeyPairGenerator.getInstance("DH");
KeyPair aliceKeyPair = generator.generateKeyPair();
byte[] alicePubKeyEncoded = aliceKeyPair.getPublic().getEncoded();

// 2. Другая сторона (например, Боб) получает открытый ключ Алисы
KeyFactory bobKeyFactory = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(alicePubKeyEncoded);
PublicKey alicePubKey = bobKeyFactory.generatePublic(x509KeySpec);
// Параметры, которые использовала Алиса при генерации ключей
DHParameterSpec dhParamFromAlicePubKey = ((DHPublicKey)alicePubKey).getParams();
// Создаёт свою пару ключей. Отдаёт свой Encoded открытый ключ
KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
bobKpairGen.initialize(dhParamFromAlicePubKey);
KeyPair bobKeyPair = bobKpairGen.generateKeyPair();
byte[] bobPubKeyEncoded = bobKeyPair.getPublic().getEncoded();

Теперь, у Алисы есть открытый ключ Боба, а у Боба есть открытый ключ Алисы. What дальше?
Как сказано в documentации JCA, у нас есть инструмент KeyAgreement, https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#KeyAgreement который позволяет установить одинаковые ключи шифрования без необходимости обмениваться секретной информацией (т.е. без обмена private key). Соглашение выглядит следующим образом:
// 3. Соглашение по протоколу Диффи-Хеллмана (Diffie–Hellman, DH)
KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
aliceKeyAgree.init(aliceKeyPair.getPrivate());
// Алиса на основе ключа боба и своего private key создаёт общий shared ключ
KeyFactory aliceKeyFactory = KeyFactory.getInstance("DH");
x509KeySpec = new X509EncodedKeySpec(bobPubKeyEncoded);
PublicKey bobPubKey = aliceKeyFactory.generatePublic(x509KeySpec);
aliceKeyAgree.doPhase(bobPubKey, true);
byte[] aliceSharedSecret = aliceKeyAgree.generateSecret();
SecretKeySpec aliceAesKey = new SecretKeySpec(aliceSharedSecret, 0, 16, "AES");
// Боб на основе ключа Алисы и своего private key создаёт общий shared ключ
KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
bobKeyAgree.init(bobKeyPair.getPrivate());
bobKeyAgree.doPhase(alicePubKey, true);
byte[] bobSharedSecret = bobKeyAgree.generateSecret();
SecretKeySpec bobAesKey = new SecretKeySpec(bobSharedSecret, 0, 16, "AES");
// Общий ключ у Алисы и Боба одинаков
System.out.println("Shared keys are equals: " + Arrays.equals(aliceSharedSecret, bobSharedSecret));

Далее Боб и Алиса, используя общий ключ, про который больше никто не знает, обмениваются зашифрованными данными:
// 4. Боб шифрует сообщение для Алисы
Cipher bobCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
bobCipher.init(Cipher.ENCRYPT_MODE, bobAesKey);
byte[] ciphertext = bobCipher.doFinal("Hello, Alice!".getBytes());
// Передаёт Алисе параметры, с которыми выполнялась шифровка
byte[] encodedParamsFromBob = bobCipher.getParameters().getEncoded();

// 5. Алиса принимает сообщение и расшифровывает его
AlgorithmParameters aesParams = AlgorithmParameters.getInstance("AES");
aesParams.init(encodedParamsFromBob);
Cipher aliceCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
aliceCipher.init(Cipher.DECRYPT_MODE, aliceAesKey, aesParams);
byte[] recovered = aliceCipher.doFinal(ciphertext);
System.out.println(new String(recovered));
This example was taken from the JCA documentation example: " Diffie-Hellman Key Exchange between 2 Parties ". This is roughly what asymmetric encryption looks like in the Java Cryptography Architecture using the Key agreement protocol. For more information about asymmetric encryption, recommended videos:
Java Cryptography Architecture: First acquaintance - 14

Certificates

Well, for dessert we still have something no less important - certificates. Typically, certificates are generated using the keytool utility included with the jdk. You can read more details, for example, here: " Generating a self-signed SSL certificate using the Java keytool command ". You can also read the manuals from Oracle. For example, here: " To Use keytool to Create a Server Certificate ". For example, let's use Tutorialspoint Java Online Compiler :
import sun.security.tools.keytool.CertAndKeyGen;
import sun.security.x509.*;
import java.security.cert.*;
import java.security.*;
// Compiler args: -XDignore.symbol.file
public class Main {
  public static void main(String[] args) throws Exception {
    CertAndKeyGen certGen = new CertAndKeyGen("RSA", "SHA256WithRSA", null);
    // generate it with 2048 bits
    certGen.generate(2048);
    PrivateKey privateKey = certGen.getPrivateKey();
    X509Key publicKey = certGen.getPublicKey();
    // prepare the validity of the certificate
    long validSecs = (long) 365 * 24 * 60 * 60; // valid for one year
    // enter your details according to your application
    X500Name principal = new X500Name("CN=My Application,O=My Organisation,L=My City,C=DE");
    // add the certificate information, currently only valid for one year.
    X509Certificate cert = certGen.getSelfCertificate(principal, validSecs);
    // Public Key from Cert equals Public Key from generator
    PublicKey publicKeyFromCert = cert.getPublicKey();
    System.out.println(publicKeyFromCert.equals(publicKey));
  }
}
As we can see, a certificate provides the ability to provide a public key. This method has a drawback - we use sun.security, which is considered risky, because... this package is not part of the public Java API. That is why during compilation it is necessary to specify the parameter - XDignore.symbol.file. There is another way - to create a certificate manually. The downside is that it uses an internal API that is not documented. However, it is useful to know about it. At a minimum, because it is clearly visible how the RFC-2459 specification is used: “ Internet X.509 Public Key Infrastructure ”. Here's an example:
// 1. Генерируем пару ключей
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(4096);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 2. Определяем данные сертификата
// Определяем срок действия сертификата
Date from = new Date();
Date to = new Date(from.getTime() + 365 * 1000L * 24L * 60L * 60L);
CertificateValidity interval = new CertificateValidity(from, to);
// Определяем subject name, т.е. Name того, с чем ассоциирован публичный ключ
// CN = Common Name. Через точку с запятой могут быть указаны также другие атрибуты
// См. https://docs.oracle.com/cd/E24191_01/common/tutorials/authz_cert_attributes.html
X500Name owner = new X500Name("cn=Unknown");
// Уникальный в пределах CA, т.е. Certificate Authority (тот, кто выдаёт сертификат) номер
BigInteger number = new BigInteger(64, new SecureRandom());
CertificateSerialNumber serialNumber = new CertificateSerialNumber(number);
// Определяем алгоритм подписи сертификата
AlgorithmId algorithmId = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
CertificateAlgorithmId certificateAlgorithmId = new CertificateAlgorithmId(algorithmId);
// 3. По подготовленной информации создаём сертификат
X509CertInfo info = new X509CertInfo();
info.set(X509CertInfo.VALIDITY, interval);
info.set(X509CertInfo.SERIAL_NUMBER, serialNumber);
info.set(X509CertInfo.SUBJECT, owner);
info.set(X509CertInfo.ISSUER, owner);
info.set(X509CertInfo.KEY, new CertificateX509Key(keyPair.getPublic()));
info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
info.set(X509CertInfo.ALGORITHM_ID, certificateAlgorithmId);
// 4. Подписываем сертификат
X509CertImpl certificate = new X509CertImpl(info);
certificate.sign(keyPair.getPrivate(), "SHA256withRSA");
// 5. Проверка сертификата
try {
	// В случае ошибки здесь будет брошено исключение. Например: java.security.SignatureException
	certificate.verify(keyPair.getPublic());
} catch (Exception e) {
	throw new IllegalStateException(e);
}
Java Cryptography Architecture: First introduction - 15

Keystore (KeyStore)

The last thing I would like to talk about is the key and certificate store, which is called KeyStore. It is clear that constantly generating certificates and keys is expensive and pointless. Therefore, they need to be stored somehow safely. There is a tool for this - KeyStore. The key store is described in the JCA documentation in the " KeyManagement " chapter. The API for working with it is very clear. Here's a small example:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
String alias = "EntityAlias";
java.security.cert.Certificate[] chain = {certificate};
keyStore.setKeyEntry(alias, keyPair.getPrivate(), "keyPassword".toCharArray(), chain);
// Загрузка содержимого (Private Key + Certificate)
Key key = keyStore.getKey(alias, "keyPassword".toCharArray());
Certificate[] certificateChain = keyStore.getCertificateChain(alias);
// Сохранение KeyStore на диск
File file = File.createTempFile("security_", ".ks");
System.out.println(file.getAbsolutePath());
try (FileOutputStream fos = new FileOutputStream(file)) {
	keyStore.store(fos, "keyStorePassword".toCharArray());
}
As you can see from the example, it is executed first loadfor the KeyStore. But in our case, we specified the first attribute as null, i.e. there is no source for KeyStore. This means the KeyStore is created empty in order to save it further. The second parameter is also null, because we are creating a new KeyStore. If we were loading KeyStore from a file, then we would need to specify a password here (similar to the KeyStore method called store).

Bottom line

So we have reviewed with you the most basic and elementary actions within the framework of the Java Cryptography Architecture (aka JCA). We saw what symmetric and asymmetric encryption is and how it is implemented in JCA. We saw how certificates and digital signatures are created and how they are used. These are all just the basics, behind which there are many more complex and interesting things. I hope this review material will be useful and will interest you in further study of this area.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION