Asymmetric encryption by Java Cryptography

By | November 28, 2017 | 1,452 Views

1. Overview

In cryptography, there are two general categories of key based algorithms:

  • Symmetric encryption algorithms: Symmetric algorithms use the same key for encryption and decryption such as the Data Encryption Standard (DES) and the Advanced Encryption Standard (AES)
  • Asymmetric (or public key) encryption algorithms: Asymmetric algorithms use two separate keys for these two operations. The most widely used is RSA (Rivest-Shamir-Adelman).

Each approach has its own benefits and drawbacks. According to Bruce Schneier, Symmetric cryptography is best for encrypting data. It is orders of magnitude faster and is not susceptible to chosen-ciphertext attacks. Public-key cryptography can do things that symmetric cryptography can’t; it is best for key management and a myriad of protocols. They solve different sorts of problems.

(Other algorithms include Digital signature, Key-agreement, One-way hash functions, and Message authentication codes.)

2. Principle of Asymmetric (public-key) Cryptosystems

  • Public and private keys: This is a pair of keys that have been selected so that if one is used for encryption, the other is used for decryption.
  • One of the two key must be kept secret.
  • Two different keys will product two different ciphertexts.
  • The sender and receiver must each have one of the matched pair of keys (not the same one).

The Cryptosystems uses the public key for encrypting plaintext input in encryption as Figure 1 while it uses the private key in authentication in Figure 2. Encrypting with a private key and decrypting with a public key is misleading and is only true for one algorithm, RSA (Bruce Schneier, 1996).

Figure 1: Asymmetric encryption (William Stallings, 1999)

Figure 2: Asymmetric authentication(William Stallings, 1999)

3. Apply Encryption algorithm – RSA

3.1 Payment Scenario

A bank service lets its public key be known to any payment providers, but keeps the private key secret. A payment provider may send a confidential message to the bank service like this:

  • The payment provider gets the bank’s public key.
  • The payment provider encrypts the message with the bank’s public key, and sends it via e.g REST API.
  • The bank decrypts the message with its private key for bank payment.

3.2 Generate key pair

KeyPairGenerator class provides how to generate pairs of public and private key. Two important arguments are a keySize argument and uses the SecureRandom implementation of the highest-priority installed provider as the source of randomness.

java/com/example/crypt/utils/GenSignature.java

package com.example.crypt.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;

import static com.example.crypt.utils.Constant.PRIVATE_KEY_STORE;
import static com.example.crypt.utils.Constant.PUBLIC_KEY_STORE;

public class GenSignature {
    private static final Logger LOGGER = LoggerFactory.getLogger(RSACryptography.class);

    private static PrivateKey privateKey;
    private static PublicKey publicKey;

    public static void generateKeyPair(int keySize, String algorithm) 
                                                throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        //SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
        SecureRandom random = SecureRandom.getInstanceStrong();
        keyPairGenerator.initialize(keySize, random);
        KeyPair keyPair =  keyPairGenerator.genKeyPair();
        privateKey = keyPair.getPrivate();
        publicKey = keyPair.getPublic();
        writeOutPublicAndPrivateKey();
    }
    private static void writeOutPublicAndPrivateKey() {
        try {
            writeToFile(PUBLIC_KEY_STORE, publicKey.getEncoded());
            writeToFile(PRIVATE_KEY_STORE, privateKey.getEncoded());
        } catch (IOException e) {
            LOGGER.debug("Having exception " + e.getMessage());
        }
    }
    private static void writeToFile(String path, byte[] key) throws IOException {
        File f = new File(path);
        f.getParentFile().mkdirs();
        try (FileOutputStream fos = new FileOutputStream(f)) {
            fos.write(key);
            fos.flush();
        }
    }
}

3.3 Implement encryption and decryption

Encrypt a plaintext and decrypt a ciphertext.
java/com/example/crypt/utils/RSACryptography.java

 public static String encrypt(PublicKey publicKey, String plaintext) 
            throws InvalidKeyException, NoSuchPaddingException, 
            NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encrypted =  cipher.doFinal(plaintext.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }

    public static String decrypt(PrivateKey privateKey, String ciphertext) 
            throws NoSuchPaddingException, NoSuchAlgorithmException, 
            InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        byte[] encrypted = Base64.getDecoder().decode(ciphertext);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(cipher.doFinal(encrypted));
    }

3.4 Build payment provider

Suppose that a user sends a request to payment provider that looks like this

POST api/payment
{
    "primaryBankAccount":"60161331926819",
    "amount": 200
}

The payment provider actually is a third-party that is not able to make any particular payment directly. Instead, it has to call the bank gateway to make the transaction. Although the bank trusts the third-party, the confident message somehow needs to be encrypted from the third-party before sending it to the bank. So the service to encrypt the message implemented at the payment provider as below:
java/com/example/crypt/service/PaymentProviderService.java

package com.example.crypt.service;

import com.example.crypt.api.dto.PaymentDTO;
import com.example.crypt.utils.RSACryptography;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;

@Service
public class PaymentProviderService {
    @Autowired
    private PublicKey publicKey;

    public String encryptPaymentMessage(PaymentDTO paymentDTO)
            throws NoSuchAlgorithmException, BadPaddingException, NoSuchPaddingException,
            IllegalBlockSizeException, InvalidKeyException, JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        String jsonInString = mapper.writeValueAsString(paymentDTO);
        return RSACryptography.encrypt(publicKey, jsonInString);
    }
}

java/com/example/crypt/api/PaymentProviderController.java

package com.example.crypt.api;

@RestController
@RequestMapping("/api/payments")
public class PaymentProviderController {

    @Autowired
    private PaymentProviderService paymentProviderService;

    @RequestMapping(value = "", method = RequestMethod.POST)
    public String encryptedPaymentMessage(@RequestBody PaymentDTO paymentDTO) 
            throws IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, 
            InvalidKeyException, BadPaddingException, JsonProcessingException {
        return paymentProviderService.encryptPaymentMessage(paymentDTO);
    }
}

Run command: mvn spring-boot:run
Try to send multi-requests with the same JSON payload and then response:

cRdTwaVh/2iBWeDBPgLtsxQfEWvl44bxW5STjAdNnekZhNXnJREMyytiqgu01d6CXt7yJpd2o6N9s
fyZnup1DhAXbtpNy8eqDF/EmB3aEIWQUh8rfDoarQD3oJnnEfjB8d79DRRZSurXXAHTkMxUqNj9B8
VlxORxQlSeXzlLkQw=

LaDMcVATSu3xgmZEzUAxYowKrMaXEDmRldVIvA7ZKug4Tcnxk1wchBLh7GET//y2pihktSGJV20R
yCloz74d3bF2qktgnAXrMicoobgEurcWZ/mQ1X5cSxn7DESgHqeQb+8TN6keHlTQxOdlbTL2UasR
VNRaUrhukORMyryfa9g=

Why are RSA ciphertexts different for the same plaintext? This is because the RSA has to use padding method, the standard OAEP which most libraries should implement. Without padding, RSA would indeed generate the same ciphertext each time.

3.5 Implement bank service

The payment provider will call the bank gateway via (SOAP, REST) as a back-end site.
Assume that the provider sends a request to the bank service as follow:

POST api/bankAccounts/payment
{
   "message":"LaDMcVATSu3xgmZEzUAxYowKrMaXEDmRldVIvA7ZKug4Tcnxk1wchBLh7GET//y2pihktSGJV20RyCloz74d3b
              F2qktgnAXrMicoobgEurcWZ/mQ1X5cSxn7DESgHqeQb+8TN6keHlTQxOdlbTL2UasRVNRaUrhukORMyryfa9g="
}

The bank receives the encrypted message and decrypts it by the bank’s private key. After that, the bank will do the payment transaction for the primaryAccountNumber.
java/com/example/crypt/service/BankAccountService.java

package com.example.crypt.service;

import com.example.crypt.api.dto.MessageDTO;
import com.example.crypt.api.dto.PaymentDTO;
import com.example.crypt.repository.BankAccount;
import com.example.crypt.repository.BankAccountRepository;
import com.example.crypt.utils.GenSignature;
import com.example.crypt.utils.RSACryptography;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.persistence.EntityNotFoundException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.util.Objects;

@Service
@Transactional
public class BankAccountService {

    @Autowired
    private BankAccountRepository bankAccountRepository;

    @Autowired
    private PrivateKey privateKey;


    public void generateKeyPair(int keySize, String algorithm) throws NoSuchAlgorithmException {
        GenSignature.generateKeyPair(keySize, algorithm);
    }

    public void decryptAndPayment(MessageDTO messageDTO)
            throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException,
            NoSuchAlgorithmException, NoSuchPaddingException, IOException {

        String plainMessage = RSACryptography.decrypt(privateKey, messageDTO.getMessage());
        PaymentDTO paymentDTO = new ObjectMapper().readValue(plainMessage, PaymentDTO.class);
        if (Objects.nonNull(paymentDTO) && StringUtils.isNotEmpty(paymentDTO.getPrimaryBankAccount())) {
            BankAccount bankAccount = bankAccountRepository
                    .findByPrimaryAccountNumber(paymentDTO.getPrimaryBankAccount())
                    .orElseThrow(() -> new EntityNotFoundException());
            double currentBalance = bankAccount.getBalance() - paymentDTO.getAmount();
            bankAccount.setBalance(currentBalance);
            bankAccountRepository.save(bankAccount);
        }
    }
}

java/com/example/crypt/api/PaymentProviderController .java

package com.example.crypt.api;

@RestController
@RequestMapping("/api/payments")
public class PaymentProviderController {

    @Autowired
    private PaymentProviderService paymentProviderService;

    @RequestMapping(value = "", method = RequestMethod.POST)
    public String encryptedPaymentMessage(@RequestBody PaymentDTO paymentDTO)
            throws IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException,
            InvalidKeyException, BadPaddingException, JsonProcessingException {
        return paymentProviderService.encryptPaymentMessage(paymentDTO);
    }
}

Retrieve the bank account and it’s balance before making any transaction in database:

primary_account_number | balance 
60161331926819           1000

Sending the request to the bank service, and then check balance again to see current balance.

primary_account_number | balance 
60161331926819           800

4. RSA Ciphertext Attack

Do people steal and descrypt the ciphertext?

The most security failures are due to failure in implementation, and not failures in aglorithms or protocal (Bruce Schneier, 1996)

In practise, there are some factors that need to be considered carefully.

  • KeySize: If n is a 1024 bit number (i.e. 308 decimal digits) then it is likely to take you 10 million mips-years. A 1GHz Pentium has about 500 mips power, so it will take 20,000 years. Many applications now use 2048 bit keys as standard. Such a number requires 1014 mips-years to factor. If you harnessed the power of 100 million computers on the internet (each providing 500 mips) on the internet, you could factor it in about 2000 years (Mark Dermot Ryan, 2008)
  • Padding scheme: the padding enables us to encrypt the plaintext to one of a large number of different possible ciphertexts. Optimal Asymmetric Encryption Padding (OAEP) is a good example.
  • Keep ingredients private: Hold Key to descrypt in a secret place, and without share any keySize, secureRandomNumber, aglorithms usage, and coding/framework to generate keyPair.

The answer should be YES but DIFFICULT.

The NSA (National Security Agency), the most security product manufacturers has some of the word’s best cryptographer working for it, but they’re not telling all they know. The reason is the resource limitation. Suppose that the NSA can read any message that it chooses, but that it cannot read all messages that it chooses (Bruce Schneier, 1996). Therefore, they have to narrow targets.

5. Summary

The example shows how to implement RSA by Java cryptography for a particular scenario, and general look about Cryptography. This is a hard topic to learn and practice. I am very happy to hear feedback and correction.

Download full code from Github

Very much base on books, and code research from my references and internet.

References

  1. Bruce Schneier, Applied Cryptography. Second Edition, J. Wiley and Sons, 1996.
  2. William Stallings, Cryptography and Network Security, Principles and Practice, Prentice Hall, 1999.
  3. Mark Dermot Ryan, Public-key cryptography, University of Birmingham. Refer at https://www.cs.bham.ac.uk/~mdr/teaching/modules/security/lectures/public_key.html
  4. Encryption and Decryption in Java Cryptography. Refer at https://www.veracode.com/blog/research/encryption-and-decryption-java-cryptography
  5. KeyPairGenerator, Oracle. https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#KeyPairGenerator
  6. Generating and Verifying Signatures, Oracle. Refer at https://docs.oracle.com/javase/tutorial/security/apisign/index.html
  7. Standard Algorithm Name Documentation for Java 8, Oracle. Refer at https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher

Leave a Reply

Your email address will not be published. Required fields are marked *