-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTLS.java
170 lines (160 loc) · 7.49 KB
/
TLS.java
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package crypto;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;
import javax.crypto.Cipher;
/**
* A rudimentary Transport Layer Security (TLS) session in pure Java, as
* described on p.462 of Cryptography: Theory and Practice, 4th Edition (2019).
*
* @author Chris Lattman
*/
public class TLS {
/**
* A basic TLS session without a trusted certificate authority.
*
* @param args not used
* @throws Exception a whole host of exceptions can be thrown, although
* they are all non-issues in this implementation
*/
public static void main(String[] args) throws Exception {
/*
* The client and the server initiate the session by introducing
* themselves. I know this is idiotic; I'm just trying to follow
* protocol here.
*
* In real TLS, this is where the client and the server agree upon
* what encryption schemes are used. This implementation chooses
* ECDSA to sign the public key, RSA to share the master secret, and
* SHA-512 to generate the two secret encryption and HMAC keys.
*/
System.out.println("Hi server, I'm the client.");
System.out.println("Hi client, I'm the server.");
/*
* A random key pair for secp256r1 is generated
*/
ECGenParameterSpec ecGen = new ECGenParameterSpec("secp256r1");
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
SecureRandom random = SecureRandom.getInstanceStrong();
keyPairGen.initialize(ecGen, random);
KeyPair keyPair = keyPairGen.generateKeyPair();
PublicKey ecdsaPublicKey = keyPair.getPublic();
/*
* An instance of RSA is generated by the server, with a 4096-bit
* public key.
*
* Note: DHE or ECDHE can be used instead of RSA for performance
* improvement. To make the connection ephemeral, the client and the
* server both need to generate new private values for each session.
*/
KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA");
rsa.initialize(4096);
KeyPair rsaKeyPair = rsa.generateKeyPair();
PublicKey publicKey = rsaKeyPair.getPublic();
PrivateKey privateKey = rsaKeyPair.getPrivate();
/*
* The ECDSA signature algorithm is used to self-sign the message.
* Here, the message is a RSA public key. The server sends the signed
* public key to the client, along with the ECDSA public key.
*
* In real TLS, a certificate is sent from the server to the client.
* The certificate contains the RSA public key, which is itself signed
* by a certificate authority.
*
* Note: if DHE/ECDHE is used instead of RSA, the server sends both a
* certificate for its ECDSA public key and its signed DHE/ECDHE public
* parameter.
*/
Signature ecSign = Signature.getInstance("SHA256withECDSA");
ecSign.initSign(keyPair.getPrivate());
ecSign.update(publicKey.getEncoded());
BigInteger signature = new BigInteger(1, ecSign.sign());
System.out.println("RSA public key: " + publicKey);
System.out.println("ECDSA public key: " + ecdsaPublicKey);
System.out.println("signature: " + signature.toString(16));
/*
* The signed public key is then verified by the client. If it
* verifies, the client generates a master secret and encrypts it with
* RSA, using the server's public key as the encryption key.
*
* Note: if DHE/ECDHE is used instead of RSA, the client verifies both
* the server's ECDSA public key and the server's DHE/ECDHE public
* parameter (using the ECDSA public key).
*/
ecSign.initVerify(ecdsaPublicKey);
ecSign.update(publicKey.getEncoded());
boolean result = ecSign.verify(signature.toByteArray());
if (result) {
System.out.println("Signature is verified.");
/*
* Set up RSA instance using the server's public key
*/
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
/*
* The master secret is generated by the client, encrypted, and
* then sent to the server.
*
* Note: if DHE/ECDHE is used instead of RSA, all the client needs
* to do is send its own DHE/ECDHE public parameter to the server
* (for added security, the client can HMAC its public parameter
* using the master secret as the HMAC key). From here, both the
* client and server can independently generate the master secret,
* and thus the session keys.
*/
BigInteger masterSecret = new BigInteger(512, random);
byte[] encrypted = cipher.doFinal(masterSecret.toByteArray());
String ciphertext = Base64.getEncoder().encodeToString(encrypted);
System.out.println("ciphertext: " + ciphertext);
/*
* The server decrypts the master secret using the RSA private key
* and then generates the encryption key and the HMAC key using a
* key derivation function (KDF), which is simply a cryptographic
* hash function.
*
* Here, SHA-512 is used to generate 2 256-bit keys by producing a
* message digest of the master secret.
*
* The client can also generate these keys since they generated
* the master secret in the first place.
*/
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] cipherTextBytes = Base64.getDecoder().decode(ciphertext);
byte[] masterSecretBytes = cipher.doFinal(cipherTextBytes);
MessageDigest hash = MessageDigest.getInstance("SHA-512");
byte[] keys = hash.digest(masterSecretBytes);
byte[] enckeybytes = new byte[32];
byte[] mackeybytes = new byte[32];
for (int i = 0; i < 32; i++) {
enckeybytes[i] = keys[i];
}
for (int j = 0; j < 32; j++) {
mackeybytes[j] = keys[j + 32];
}
BigInteger encrypt = new BigInteger(1, enckeybytes);
BigInteger hmac = new BigInteger(1, mackeybytes);
/*
* The encryption key and HMAC keys are printed for demonstration
* only. This should NEVER be done in practice.
*
* In real TLS, ciphertexts are sent between the client and the
* server (encrypted with these secret keys) to verify that they
* share the same keys. If successful, all future communications
* between them during the session are encrypted using these keys.
*/
System.out.println("shared encryption key: " +
encrypt.toString(16));
System.out.println("shared HMAC key: " + hmac.toString(16));
}
else {
System.out.println("Signature is not verified.");
}
}
}