Preface
There are several common encryption algorithms in programming, and they each have applications in different scenarios. All encryption methods except the message digest algorithm will require a key.
- Message digest algorithm
- Symmetric encryption algorithm
- Asymmetric encryption algorithm
Key
A key (key, also often called golden key) is some secret information used to accomplish encryption, decryption, integrity verification, and other cryptographic applications.
Key classification
- Keys in encryption and decryption: share the same key in symmetric encryption, split into public key and private key in asymmetric encryption, public key encryption private key decryption.
- Keys in message authentication code and digital signature: In message authentication code, the message sender and receiver use a shared key for authentication. In digital signatures, the signature uses the private key, and the verification uses the public key.
- Session key and master key: A key that is used only once per communication is called a session key. A key that is used repeatedly as opposed to a session key is called a master key.
Keys and Passwords
Passwords are generally generated by the user, are readable, can be remembered and stored, and are often used for software management, while keys are used by software that implements encryption algorithms and do not need to have readability (though they are Base64 for ease of reading in programming). We can also generate keys by passwords.
Key management
- Generate keys: you can generate keys with random numbers or with passphrases.
- Distribute keys: you can use prior shared keys, use key distribution centers, use public key ciphers, use Diffie-Hellman key exchange.
- Update keys
- Save the key
- Revoke keys
Key generation
jce (Java Cryptography Extension) in jdk contains all APIs related to cryptography.
Generate the key for the symmetric encryption algorithm.
1
2
3
4
5
6
7
8
9
10
11
|
public static SecretKey generateKey(int keySize) {
KeyGenerator keyGenerator;
try {
keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(keySize);
return keyGenerator.generateKey();
} catch (NoSuchAlgorithmException e) {
// ignore
return null;
}
}
|
Generate keys for symmetric asymmetric encryption algorithms.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/**
* 生成非对称密钥对
*
* @param keySize 密钥大小
* @param random 指定随机来源,默认使用 JCAUtil.getSecureRandom()
* @return 非对称密钥对
* @throws NoSuchAlgorithmException NoSuchAlgorithm
*/
public static PPKeys genKeysRSA(int keySize, SecureRandom random) throws NoSuchAlgorithmException {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
if (null != random) {
generator.initialize(keySize, random);
} else {
generator.initialize(keySize);
}
KeyPair pair = generator.generateKeyPair();
PPKeys keys = new PPKeys();
PublicKey publicKey = pair.getPublic();
PrivateKey privateKey = pair.getPrivate();
keys.setPublicKey(Base64.getEncoder().encodeToString(publicKey.getEncoded()));
keys.setPrivateKey(Base64.getEncoder().encodeToString(privateKey.getEncoded()));
return keys;
}
|
Key Negotiation (Diffie-Hellman)
Key negotiation is a protocol by which two or more parties establish the same shared key and then communicate with each other in a symmetric encrypted transmission without exchanging keys.
The general process: each party generates a public-private key pair and distributes the public key to the other parties, and the shared key can be computed offline when both parties get a copy of the other party’s public key.
Java provides KeyAgreement
to implement key negotiation.
- Alice and Bob each initialize their own key negotiation object
KeyAgreement
with their private keys, calling the init()
method.
- Then pass the public key of each communicating party into the execution
doPhase(Key key, boolean lastPhase)
.
- Each party generates a shared key
generateSecret()
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public static void diffieHellman() throws Exception {
AlgorithmParameterGenerator dhParams = AlgorithmParameterGenerator.getInstance("DH");
dhParams.init(1024);
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DH");
keyGen.initialize(dhParams.generateParameters().getParameterSpec(DHParameterSpec.class), new SecureRandom());
KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
KeyPair alicePair = keyGen.generateKeyPair();
KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
KeyPair bobPair = keyGen.generateKeyPair();
aliceKeyAgree.init(alicePair.getPrivate());
bobKeyAgree.init(bobPair.getPrivate());
aliceKeyAgree.doPhase(bobPair.getPublic(), true);
bobKeyAgree.doPhase(alicePair.getPublic(), true);
boolean agree = Base64.getEncoder().encodeToString(aliceKeyAgree.generateSecret()).equals(
Base64.getEncoder().encodeToString(bobKeyAgree.generateSecret())
);
System.out.println(agree);
}
|
Message Digest Algorithm
Message digest algorithms, also known as cryptographic hashing algorithms, do not require a key for the encryption process. Common cryptographic hashing algorithms are the MD series and the SHA series.
An ideal cryptographic hash function should have the following properties.
- The output of any incoming message is always of fixed length.
- The message digest appears to be “random”, so that it is difficult to infer the value from the original message.
- A good hash function should have a very low collision probability, i.e., the probability of getting the same value for different messages.
MD series
MD5 Message-Digest Algorithm (MD5 Message-Digest Algorithm), a widely used cryptographic hash function, outputs a 128-bit (16-byte) hash value (hash value), MD5 was originally designed as a cryptographic hash function, and it is currently found to have a large number of vulnerabilities, so it is not recommended for direct use as encryption, however However, it is still widely used in non-encrypted scenarios such as data integrity checking and file integrity checking.
1
2
3
4
5
6
7
8
9
|
public static String md5(String content) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] bytes = digest.digest(content.getBytes(StandardCharsets.UTF_8));
return Hex.encodeHexString(bytes);
} catch (final NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
}
|
SHA Family
Secure Hash Algorithm (SHA for short) is a family of cryptographic hash functions that are certified by FIPS (Federal Information Processing Standards) as secure hash algorithms. It is an algorithm that calculates a string of fixed length (also called message digest) corresponding to a digital message. If the input messages are different, the chance that they correspond to different strings is high.
They contain SHA-0, SHA-1, SHA-2, SHA-3
, where SHA-0, SHA-1
output length is 160 bits, and SHA-2
contains SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256
, which we usually use. SHA-256
.
1
2
3
4
5
6
7
8
9
|
public static String sha256(String content) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] bytes = digest.digest(content.getBytes(StandardCharsets.UTF_8));
return Hex.encodeHexString(bytes);
} catch (final NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
}
|
Symmetric encryption algorithm
Symmetric encryption algorithm, both sides hold the same key for encryption and decryption, common symmetric encryption algorithm: DES
3DES
AES128
AES192
AES256
. Understanding symmetric encryption requires first understanding the following concepts.
- Grouped cipher mode : The plaintext is cut for encryption, and then the ciphertext is stitched together. For example, in AES, the plaintext data is cut into blocks of 16 bytes in size, and when the last block is not enough for 16 bytes, Padding mode is used to supplement it.
- Padding : It has three modes PKCS5, PKCS7 and NOPADDING, PKCS5 is padded with the number of missing bytes, for example, if 5 bytes are missing, 5 number 5 is padded, and PKCS7 is padded with 0 for the number of missing bytes. If the data happens to be an integer multiple of 16, PKCS5 and PKCS7 will be supplemented with another 16 bytes of data to distinguish between padding and valid data, NOPADDING mode does not require padding.
- Initialization vector : The purpose of initial vector IV is to make the encryption more secure and reliable, and the IV size corresponds to the data block length in group cipher mode.
- encryption mode: four encryption modes are: ECB (electronic cipher book mode), CBC (cipher group link mode), CFB, OFB. ECB mode is only using plaintext and key to encrypt data, so the mode does not require Padding, security is also weak, CBC mode data chunking and use the incoming IV in order to carry out the heterogeneous operation, security is also CBC mode is relatively high security, so CBC mode is generally chosen at present.
- encryption key: Different encryption algorithms have different key lengths, for example: DES default length 56 bits, 3DES default length 168 bits, also supports 128 bits, AES default 128 bits, also supports 192 bits, 256 bits. We generally generate the key according to the password, and the password length needs to meet the algorithm key length.
DES
DES
is a typical algorithm in the field of symmetric encryption algorithms, because the default length of the key is 56 bit
, so the length of the cipher needs to be larger than 8 byte
, DESKeySpec
takes the first 8 byte
for key making.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public static String encryptDES(byte[] content, String password) {
try {
SecureRandom random = new SecureRandom();
DESKeySpec desKeySpec = new DESKeySpec(password.getBytes());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, random);
return Base64.getEncoder().encodeToString(cipher.doFinal(content));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String decryptDES(String content, String password) throws Exception {
SecureRandom random = new SecureRandom();
DESKeySpec desKeySpec = new DESKeySpec(password.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE, secretKey, random);
return new String(cipher.doFinal(Base64.getDecoder().decode(content)));
}
|
3DES
3DES (or Triple DES). It is an enhancement of DES algorithm, which uses three 56-bit keys to encrypt data three times. It takes DES as the basic module and designs a packet encryption algorithm by combining grouping methods. Compared with the original DES, 3DES is more secure. The default length of the key is 168 bit
, the password needs to be larger than 24 byte
, and the IV is an 8 byte
array of random numbers and letters.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public static String encrypt3DESECB(String content, String key, String iv) {
try {
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
DESedeKeySpec dks = new DESedeKeySpec(key.getBytes(StandardCharsets.UTF_8));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey secretkey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretkey, ivSpec);
return Base64.getEncoder().encodeToString(cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String decrypt3DESECB(String content, String key, String iv) {
try {
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
DESedeKeySpec dks = new DESedeKeySpec(key.getBytes(StandardCharsets.UTF_8));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey secretkey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretkey, ivSpec);
return new String(cipher.doFinal(Base64.getDecoder().decode(content)), StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
|
AES
AES Advanced Data Encryption Standard, which is effective against all known attacks against the DES algorithm, has a default key length of 128 bit
and can also be selected from 192 bit
and 256 bit
. AES-128
AES-192
AES-256
Default AES-128
, use PBEKeySpec
to generate a fixed size key.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
public static String encryptAES128(String plainText, String password, String salt) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] saltBytes = salt.getBytes(StandardCharsets.UTF_8);
// AES-128 密钥长度为128bit
PBEKeySpec spec = new PBEKeySpec(
password.toCharArray(),
saltBytes,
1000,
128
);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AlgorithmParameters params = cipher.getParameters();
IvParameterSpec iv = params.getParameterSpec(IvParameterSpec.class);
cipher.init(Cipher.ENCRYPT_MODE, secret, iv);
byte[] encryptedTextBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
String encodedText = Base64.getEncoder().encodeToString(encryptedTextBytes);
String encodedIV = Base64.getEncoder().encodeToString(iv.getIV());
String encodedSalt = Base64.getEncoder().encodeToString(saltBytes);
return encodedSalt + "." + encodedIV + "." + encodedText;
}
public static String decryptAES128(String encryptedText, String password) throws Exception {
String[] fields = encryptedText.split("\\.");
byte[] saltBytes = Base64.getDecoder().decode(fields[0]);
byte[] ivBytes = Base64.getDecoder().decode(fields[1]);
byte[] encryptedTextBytes = Base64.getDecoder().decode(fields[2]);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(
password.toCharArray(),
saltBytes,
1000,
128
);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes));
byte[] decryptedTextBytes;
try {
decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
return new String(decryptedTextBytes);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException(e);
}
}
|
The following exception may occur when using AES-256
.
1
|
java.security.InvalidKeyException: Illegal key size
|
JDK 1.8.0_161 and above have unlimited strength encryption enabled by default.
1
2
3
|
static {
java.security.Security.setProperty("crypto.policy", "unlimited");
}
|
JDK 1.8.0_161 Previous versions require manual installation of jce strategy file (download address)
Asymmetric encryption algorithm
Asymmetric encryption uses a key pair, the public key for encryption and the private key for decryption. Regarding key size, as of 2020, the largest publicly known RSA key that is cracked is the 829-bit RSA-250, and it is recommended to use at least 2048-bit keys.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public static String encrypt(byte[] publicKey, String plainText) {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
KeyFactory kf;
try {
kf = KeyFactory.getInstance("RSA");
PublicKey publicKeySecret = kf.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKeySecret);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
return new String(Base64.getEncoder().encode(encryptedBytes));
} catch (Exception e) {
log.error("Rsa encrypt error ", e);
throw new RuntimeException(e);
}
}
public static String decrypt(byte[] privateKey, String encryptedText) {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory kf;
try {
kf = KeyFactory.getInstance("RSA");
PrivateKey privateKeySecret = kf.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKeySecret);
return new String(cipher.doFinal(Base64.getDecoder().decode(encryptedText)), StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("Rsa decrypt error ", e);
throw new RuntimeException(e);
}
}
|