| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| |
| |
| |
| /* |
| * Implementation of the AES-GCM Encryption/Authentication |
| * |
| * Some restrictions.. |
| * 1. Only for use with AES |
| * 2. Returned tag is always 128-bits. Truncate at your own risk. |
| * 3. The order of function calls must follow some rules |
| * |
| * Typical sequence of calls.. |
| * 1. call GCM_init |
| * 2. call GCM_add_header any number of times, as long as length of header is multiple of 16 bytes (block size) |
| * 3. call GCM_add_header one last time with any length of header |
| * 4. call GCM_add_cipher any number of times, as long as length of cipher/plaintext is multiple of 16 bytes |
| * 5. call GCM_add_cipher one last time with any length of cipher/plaintext |
| * 6. call GCM_finish to extract the tag. |
| * |
| * See http://www.mindspring.com/~dmcgrew/gcm-nist-6.pdf |
| */ |
| |
| public class GCM |
| { |
| public const int NB = 4; |
| public const int GCM_ACCEPTING_HEADER = 0; |
| public const int GCM_ACCEPTING_CIPHER = 1; |
| public const int GCM_NOT_ACCEPTING_MORE = 2; |
| public const int GCM_FINISHED = 3; |
| public const int GCM_ENCRYPTING = 0; |
| public const int GCM_DECRYPTING = 1; |
| |
| //JAVA TO C# CONVERTER NOTE: The following call to the 'RectangularArrays' helper class reproduces the rectangular array initialization that is automatic in Java: |
| //ORIGINAL LINE: private int[][] table = new int[128][4]; // 2k bytes |
| private int[][] table = RectangularArrays.ReturnRectangularIntArray(128, 4); // 2k bytes |
| private sbyte[] stateX = new sbyte[16]; |
| private sbyte[] Y_0 = new sbyte[16]; |
| private int counter; |
| private int[] lenA = new int[2]; |
| private int[] lenC = new int[2]; |
| private int status; |
| private AES a = new AES(); |
| |
| private static int pack(sbyte[] b) |
| { // pack bytes into a 32-bit Word |
| return ((((int)b[0]) & 0xff) << 24) | (((int)b[1] & 0xff) << 16) | (((int)b[2] & 0xff) << 8) | ((int)b[3] & 0xff); |
| } |
| |
| private static sbyte[] unpack(int a) |
| { // unpack bytes from a word |
| sbyte[] b = new sbyte[4]; |
| b[3] = (sbyte)(a); |
| b[2] = (sbyte)((int)((uint)a >> 8)); |
| b[1] = (sbyte)((int)((uint)a >> 16)); |
| b[0] = (sbyte)((int)((uint)a >> 24)); |
| return b; |
| } |
| |
| private void precompute(sbyte[] H) |
| { |
| int i, j, c; |
| sbyte[] b = new sbyte[4]; |
| |
| for (i = j = 0;i < NB;i++,j += 4) |
| { |
| b[0] = H[j]; |
| b[1] = H[j + 1]; |
| b[2] = H[j + 2]; |
| b[3] = H[j + 3]; |
| table[0][i] = pack(b); |
| } |
| for (i = 1;i < 128;i++) |
| { |
| c = 0; |
| for (j = 0;j < NB;j++) |
| { |
| table[i][j] = c | (int)((uint)(table[i - 1][j]) >> 1); |
| c = table[i - 1][j] << 31; |
| } |
| if (c != 0) |
| { |
| table[i][0] ^= unchecked((int)0xE1000000); // irreducible polynomial |
| } |
| } |
| } |
| |
| private void gf2mul() |
| { // gf2m mul - Z=H*X mod 2^128 |
| int i, j, m, k; |
| int[] P = new int[4]; |
| int c; |
| sbyte[] b; //=new byte[4]; |
| |
| P[0] = P[1] = P[2] = P[3] = 0; |
| j = 8; |
| m = 0; |
| for (i = 0;i < 128;i++) |
| { |
| c = ((int)((uint)stateX[m] >> (--j))) & 1; |
| if (c != 0) |
| { |
| for (k = 0;k < NB;k++) |
| { |
| P[k] ^= table[i][k]; |
| } |
| } |
| if (j == 0) |
| { |
| j = 8; |
| m++; |
| if (m == 16) |
| { |
| break; |
| } |
| } |
| } |
| for (i = j = 0;i < NB;i++,j += 4) |
| { |
| b = unpack(P[i]); |
| stateX[j] = b[0]; |
| stateX[j + 1] = b[1]; |
| stateX[j + 2] = b[2]; |
| stateX[j + 3] = b[3]; |
| } |
| } |
| |
| private void wrap() |
| { // Finish off GHASH |
| int i, j; |
| int[] F = new int[4]; |
| sbyte[] L = new sbyte[16]; |
| sbyte[] b; //=new byte[4]; |
| |
| /* convert lengths from bytes to bits */ |
| F[0] = (lenA[0] << 3) | (int)((uint)(lenA[1] & 0xE0000000)>>29); |
| F[1] = lenA[1] << 3; |
| F[2] = (lenC[0] << 3) | (int)((uint)(lenC[1] & 0xE0000000)>>29); |
| F[3] = lenC[1] << 3; |
| for (i = j = 0;i < NB;i++,j += 4) |
| { |
| b = unpack(F[i]); |
| L[j] = b[0]; |
| L[j + 1] = b[1]; |
| L[j + 2] = b[2]; |
| L[j + 3] = b[3]; |
| } |
| for (i = 0;i < 16;i++) |
| { |
| stateX[i] ^= L[i]; |
| } |
| gf2mul(); |
| } |
| |
| /* Initialize GCM mode */ |
| public virtual void init(sbyte[] key, int niv, sbyte[] iv) |
| { // iv size niv is usually 12 bytes (96 bits). AES key size nk can be 16,24 or 32 bytes |
| int i; |
| sbyte[] H = new sbyte[16]; |
| sbyte[] b; //=new byte[4]; |
| |
| for (i = 0;i < 16;i++) |
| { |
| H[i] = 0; |
| stateX[i] = 0; |
| } |
| |
| a.init(AES.ECB,key,iv); |
| a.ecb_encrypt(H); // E(K,0) |
| precompute(H); |
| |
| lenA[0] = lenC[0] = lenA[1] = lenC[1] = 0; |
| if (niv == 12) |
| { |
| for (i = 0;i < 12;i++) |
| { |
| a.f[i] = iv[i]; |
| } |
| b = unpack((int)1); |
| a.f[12] = b[0]; |
| a.f[13] = b[1]; |
| a.f[14] = b[2]; |
| a.f[15] = b[3]; // initialise IV |
| for (i = 0;i < 16;i++) |
| { |
| Y_0[i] = a.f[i]; |
| } |
| } |
| else |
| { |
| status = GCM_ACCEPTING_CIPHER; |
| ghash(iv,niv); // GHASH(H,0,IV) |
| wrap(); |
| for (i = 0;i < 16;i++) |
| { |
| a.f[i] = stateX[i]; |
| Y_0[i] = a.f[i]; |
| stateX[i] = 0; |
| } |
| lenA[0] = lenC[0] = lenA[1] = lenC[1] = 0; |
| } |
| status = GCM_ACCEPTING_HEADER; |
| } |
| |
| /* Add Header data - included but not encrypted */ |
| public virtual bool add_header(sbyte[] header, int len) |
| { // Add some header. Won't be encrypted, but will be authenticated. len is length of header |
| int i , j = 0; |
| if (status != GCM_ACCEPTING_HEADER) |
| { |
| return false; |
| } |
| |
| while (j < len) |
| { |
| for (i = 0;i < 16 && j < len;i++) |
| { |
| stateX[i] ^= header[j++]; |
| lenA[1]++; |
| if (lenA[1] == 0) |
| { |
| lenA[0]++; |
| } |
| } |
| gf2mul(); |
| } |
| if (len % 16 != 0) |
| { |
| status = GCM_ACCEPTING_CIPHER; |
| } |
| return true; |
| } |
| |
| private bool ghash(sbyte[] plain, int len) |
| { |
| int i , j = 0; |
| int counter; |
| // byte[] B=new byte[16]; |
| // byte[] b=new byte[4]; |
| |
| if (status == GCM_ACCEPTING_HEADER) |
| { |
| status = GCM_ACCEPTING_CIPHER; |
| } |
| if (status != GCM_ACCEPTING_CIPHER) |
| { |
| return false; |
| } |
| |
| while (j < len) |
| { |
| for (i = 0;i < 16 && j < len;i++) |
| { |
| stateX[i] ^= plain[j++]; |
| lenC[1]++; |
| if (lenC[1] == 0) |
| { |
| lenC[0]++; |
| } |
| } |
| gf2mul(); |
| } |
| if (len % 16 != 0) |
| { |
| status = GCM_NOT_ACCEPTING_MORE; |
| } |
| return true; |
| } |
| |
| /* Add Plaintext - included and encrypted */ |
| public virtual sbyte[] add_plain(sbyte[] plain, int len) |
| { |
| int i , j = 0; |
| int counter; |
| sbyte[] B = new sbyte[16]; |
| sbyte[] b = new sbyte[4]; |
| sbyte[] cipher = new sbyte[len]; |
| |
| if (status == GCM_ACCEPTING_HEADER) |
| { |
| status = GCM_ACCEPTING_CIPHER; |
| } |
| if (status != GCM_ACCEPTING_CIPHER) |
| { |
| return new sbyte[0]; |
| } |
| |
| while (j < len) |
| { |
| |
| b[0] = a.f[12]; |
| b[1] = a.f[13]; |
| b[2] = a.f[14]; |
| b[3] = a.f[15]; |
| counter = pack(b); |
| counter++; |
| b = unpack(counter); |
| a.f[12] = b[0]; |
| a.f[13] = b[1]; |
| a.f[14] = b[2]; |
| a.f[15] = b[3]; // increment counter |
| for (i = 0;i < 16;i++) |
| { |
| B[i] = a.f[i]; |
| } |
| a.ecb_encrypt(B); // encrypt it |
| |
| for (i = 0;i < 16 && j < len;i++) |
| { |
| cipher[j] = (sbyte)(plain[j] ^ B[i]); |
| stateX[i] ^= cipher[j++]; |
| lenC[1]++; |
| if (lenC[1] == 0) |
| { |
| lenC[0]++; |
| } |
| } |
| gf2mul(); |
| } |
| if (len % 16 != 0) |
| { |
| status = GCM_NOT_ACCEPTING_MORE; |
| } |
| return cipher; |
| } |
| |
| /* Add Ciphertext - decrypts to plaintext */ |
| public virtual sbyte[] add_cipher(sbyte[] cipher, int len) |
| { |
| int i , j = 0; |
| int counter; |
| sbyte[] B = new sbyte[16]; |
| sbyte[] b = new sbyte[4]; |
| sbyte[] plain = new sbyte[len]; |
| |
| if (status == GCM_ACCEPTING_HEADER) |
| { |
| status = GCM_ACCEPTING_CIPHER; |
| } |
| if (status != GCM_ACCEPTING_CIPHER) |
| { |
| return new sbyte[0]; |
| } |
| |
| while (j < len) |
| { |
| |
| b[0] = a.f[12]; |
| b[1] = a.f[13]; |
| b[2] = a.f[14]; |
| b[3] = a.f[15]; |
| counter = pack(b); |
| counter++; |
| b = unpack(counter); |
| a.f[12] = b[0]; |
| a.f[13] = b[1]; |
| a.f[14] = b[2]; |
| a.f[15] = b[3]; // increment counter |
| for (i = 0;i < 16;i++) |
| { |
| B[i] = a.f[i]; |
| } |
| a.ecb_encrypt(B); // encrypt it |
| for (i = 0;i < 16 && j < len;i++) |
| { |
| plain[j] = (sbyte)(cipher[j] ^ B[i]); |
| stateX[i] ^= cipher[j++]; |
| lenC[1]++; |
| if (lenC[1] == 0) |
| { |
| lenC[0]++; |
| } |
| } |
| gf2mul(); |
| } |
| if (len % 16 != 0) |
| { |
| status = GCM_NOT_ACCEPTING_MORE; |
| } |
| return plain; |
| } |
| |
| /* Finish and extract Tag */ |
| public virtual sbyte[] finish(bool extract) |
| { // Finish off GHASH and extract tag (MAC) |
| int i; |
| sbyte[] tag = new sbyte[16]; |
| |
| wrap(); |
| /* extract tag */ |
| if (extract) |
| { |
| a.ecb_encrypt(Y_0); // E(K,Y0) |
| for (i = 0;i < 16;i++) |
| { |
| Y_0[i] ^= stateX[i]; |
| } |
| for (i = 0;i < 16;i++) |
| { |
| tag[i] = Y_0[i]; |
| Y_0[i] = stateX[i] = 0; |
| } |
| } |
| status = GCM_FINISHED; |
| a.end(); |
| return tag; |
| } |
| |
| public static sbyte[] hex2bytes(string s) |
| { |
| int len = s.Length; |
| sbyte[] data = new sbyte[len / 2]; |
| for (int i = 0; i < len; i += 2) |
| { |
| data[i / 2] = (sbyte)((char.digit(s[i], 16) << 4) + char.digit(s[i + 1], 16)); |
| } |
| return data; |
| } |
| |
| /* |
| public static void main(String[] args) { |
| int i; |
| |
| String KT="feffe9928665731c6d6a8f9467308308"; |
| String MT="d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"; |
| String HT="feedfacedeadbeeffeedfacedeadbeefabaddad2"; |
| // char* NT="cafebabefacedbaddecaf888"; |
| // Tag should be 5bc94fbc3221a5db94fae95ae7121a47 |
| String NT="9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b"; |
| // Tag should be 619cc5aefffe0bfa462af43c1699d050 |
| |
| |
| byte[] T=new byte[16]; // Tag |
| byte[] K=new byte[16]; // AES Key |
| byte[] H=new byte[64]; // Header - to be included in Authentication, but not encrypted |
| byte[] N=new byte[100]; // IV - Initialisation vector |
| byte[] M=new byte[100]; // Plaintext to be encrypted/authenticated |
| byte[] C=new byte[100]; // Ciphertext |
| byte[] P=new byte[100]; // Recovered Plaintext |
| |
| GCM g=new GCM(); |
| |
| M=hex2bytes(MT); |
| H=hex2bytes(HT); |
| N=hex2bytes(NT); |
| K=hex2bytes(KT); |
| |
| int len=M.length; |
| int lenH=H.length; |
| int lenK=K.length; |
| int lenIV=N.length; |
| |
| System.out.format("Plaintext=\n"); |
| for (i=0;i<len;i++) System.out.format("%02x",M[i]); |
| System.out.format("\n"); |
| |
| g.init(K,lenIV,N); |
| g.add_header(H,lenH); |
| C=g.add_plain(M,len); |
| T=g.finish(true); |
| |
| System.out.format("Ciphertext=\n"); |
| for (i=0;i<len;i++) System.out.format("%02x",C[i]); |
| System.out.format("\n"); |
| |
| System.out.format("Tag=\n"); |
| for (i=0;i<16;i++) System.out.format("%02x",T[i]); |
| System.out.format("\n"); |
| |
| g.init(K,lenIV,N); |
| g.add_header(H,lenH); |
| P=g.add_cipher(C,len); |
| T=g.finish(true); |
| |
| System.out.format("Plaintext=\n"); |
| for (i=0;i<len;i++) System.out.format("%02x",P[i]); |
| System.out.format("\n"); |
| |
| System.out.format("Tag=\n"); |
| for (i=0;i<16;i++) System.out.format("%02x",T[i]); |
| System.out.format("\n"); |
| } */ |
| } |