· 6 years ago · Jun 27, 2019, 03:30 PM
1import java.nio.ByteBuffer;
2import java.nio.charset.Charset;
3import java.security.Key;
4import java.security.MessageDigest;
5import java.security.SecureRandom;
6import java.security.spec.KeySpec;
7import java.util.Arrays;
8
9import javax.crypto.Cipher;
10import javax.crypto.SecretKeyFactory;
11import javax.crypto.spec.IvParameterSpec;
12import javax.crypto.spec.PBEKeySpec;
13import javax.crypto.spec.PBEParameterSpec;
14import javax.xml.bind.DatatypeConverter;
15
16public class Solution {
17
18 private static final String ALGORITHM = "PBEWITHHMACSHA512ANDAES_128";
19 private static final Charset charset = Charset.forName("utf-8");
20 private static final int ITERATION_COUNT = 100;
21
22 private static byte[] getIvAndMac(String message, String key, byte[] salt) {
23 try {
24 KeySpec spec = new PBEKeySpec(key.toCharArray(), salt, ITERATION_COUNT);
25 // salt is also used as IV
26 IvParameterSpec ivSpec = new IvParameterSpec(salt);
27 PBEParameterSpec pbSpec = new PBEParameterSpec(salt, ITERATION_COUNT, ivSpec);
28 Key secretKey = SecretKeyFactory.getInstance(ALGORITHM).generateSecret(spec);
29 Cipher eCipher = Cipher.getInstance(ALGORITHM);
30 eCipher.init(Cipher.ENCRYPT_MODE, secretKey, pbSpec);
31
32 byte[] mac = eCipher.doFinal(message.getBytes(charset));
33 // then hash the MAC so that it will always be of fixed length
34 MessageDigest md = MessageDigest.getInstance("SHA");
35 byte[] hash = md.digest(mac);
36 ByteBuffer bb = ByteBuffer.allocate(salt.length + hash.length);
37 bb.put(salt);
38 bb.put(hash);
39
40 return bb.array();
41 }
42 catch(Exception e) {
43 throw new RuntimeException(e);
44 }
45 }
46
47 // Add a message autentication code to the message using a specific key.
48 public static String addMac(String message, String key, int messagelength) {
49 // here the messagelength property is not used at all, not sure why it is even passed when the whole message needs to be MACed
50
51 // salt is to keep the generated MAC unique even when the same message is passed as input
52 // instead of using a counter which requires storing the messages for future reference to detect duplicates
53 byte[] salt = new byte[16];
54 SecureRandom random = new SecureRandom();
55 random.nextBytes(salt);
56
57 byte[] ivAndMac = getIvAndMac(message, key, salt);
58
59 return message + DatatypeConverter.printHexBinary(ivAndMac);
60 }
61
62 // Verify a message authentication code to the message given a specific key.
63 public static boolean checkMac(String message, String key, int messagelength) {
64 if(message == null) {
65 return false;
66 }
67
68 if(messagelength > message.length()) {
69 return false;
70 }
71
72 String plainText = message.substring(0, messagelength);
73 String macHex = message.substring(messagelength);
74 byte[] ivAndMac = DatatypeConverter.parseHexBinary(macHex);
75 if(ivAndMac.length < 36) { // salt itself is 16 bytes + hash is 20 bytes
76 return false;
77 }
78
79 byte[] salt = new byte[16];
80 System.arraycopy(ivAndMac, 0, salt, 0, 16);
81
82 byte[] reComputedIvAndMac = getIvAndMac(plainText, key, salt);
83
84 return Arrays.equals(ivAndMac, reComputedIvAndMac);
85 }
86
87 public static void main(String[] args) {
88 String message = "this is a secret message";
89 String key = "and this is my 32 character secret key";
90
91 String authenticatedMessage = addMac(message, key, message.length());
92 //System.out.println(authenticatedMessage);
93 boolean verified = checkMac(authenticatedMessage, key, message.length());
94 //System.out.println(verified);
95 if(!verified) {
96 System.out.println("corrupted message, MAC check failed");
97 }
98
99 String anotherAuthMessageWithSameMessage = addMac(message, key, message.length());
100 if(authenticatedMessage.equals(anotherAuthMessageWithSameMessage)) {
101 System.out.println("program failed to generate unique MAC for the same input");
102 }
103
104 // check MAC by altering the message
105 verified = checkMac("abc" + anotherAuthMessageWithSameMessage, key, anotherAuthMessageWithSameMessage.length() + 3);
106 if(verified) {
107 System.out.println("MAC check should fail");
108 }
109 }
110}