SMS4 소개:
이 알고리즘은 그룹화 알고리즘입니다. 이 알고리즘은 패킷 길이 128비트, 키 길이 128비트(총 16바이트)를 가집니다. 암호화 알고리즘과 키 확장 알고리즘 모두 32라운드 비선형 반복 구조를 채택합니다. 복호화 알고리즘은 암호화 알고리즘과 동일한 구조를 가지지만, 라운드 키의 사용 순서가 반대이며, 복호화 휠 키는 암호화 휠 키의 역순입니다. SMS4의 모든 기본 클래스에서 암호화와 복호화의 기본 기능은 동일하지만, 암호화되었는지 복호화되었는지 판단하기 위해 int 타입 플래그 비트가 필요합니다.
SMS4 암호화 알고리즘 기본 사항:
공용 클래스 SMS4 {
개인 정적 최종 정정 정체 ENCRYPT = 1; 개인 정적 최종 정수 DECRYPT = 0; 공개 정적 결승 정치 라운드 = 32; 개인 정적 최종 정수 BLOCK = 16;
개인 바이트[] Sbox = { (바이트) 0xd6, (바이트) 0x90, (바이트) 0xe9, (바이트) 0xfe, (바이트) 0xcc, (바이트) 0xe1, 0x3d, (바이트) 0xb7, 0x16, (바이트) 0xb6, 0x14, (바이트) 0xc2, 0x28, (바이트) 0xfb, 0x2c, 0x05, 0x2b, 0x67, (바이트) 0x9a, 0x76, 0x2a, (바이트) 0xbe, 0x04, (바이트) 0xc3, (바이트) 0xaa, 0x44, 0x13, 0x26, 0x49, (바이트) 0x86, 0x06, (비트) 0x99, (바이트) 0x9c, 0x42, 0x50, (바이트) 0xf4, (바이트) 0x91, (바이트) 0xef, (바이트) 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, (비트) 0xed, (바이트) 0xcf, (바이트) 0xac, 0x62, (바이트) 0xe4, (바이트) 0xb3, 0x1c, (바이트) 0xa9, (바이트) 0xc9, 0x08, (바이트) 0xe8, (비트) 0x95, (바이트) 0x80, (바이트) 0xdf, (바이트) 0x94, (바이트) 0xfa, 0x75, (비트) 0x8f, 0x3f, (바이트) 0xa6, 0x47, 0x07, (바이트) 0xa7, (바이트) 0xfc, (바이트) 0xf3, 0x73, 0x17, (바이트) 0xba, (바이트) 0x83, 0x59, 0x3c, 0x19, (바이트) 0xe6, (바이트) 0x85, 0x4f, (바이트) 0xa8, 0x68, 0x6b, (바이트) 0x81, (바이트) 0xb2, 0x71, 0x64, (바이트) 0xda, (바이트) 0x8b, (바이트) 0xf8, (바이트) 0xeb, 0x0f, 0x4b, 0x70, 0x56, (바이트) 0x9d, 0x35, 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, (바이트) 0xd1, (바이트) 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, (바이트) 0x87, (바이트) 0xd4, 0x00, 0x46, 0x57, (바이트) 0x9f, (바이트) 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, (바이트) 0xe7, (바이트) 0xa0, (바이트) 0xc4, (바이트) 0xc8, (바이트) 0x9e, (바이트) 0xea, (바이트) 0xbf, (바이트) 0x8a, (바이트) 0xd2, 0x40, (바이트) 0xc7, 0x38, (바이트) 0xb5, (바이트) 0xa3, (바이트) 0xf7, (바이트) 0xf2, (바이트) 0xce, (바이트) 0xf9, 0x61, 0x15, (비트) 0xa1, (바이트) 0xe0, (바이트) 0xae, 0x5d, (바이트) 0xa4, (바이트) 0x9b, 0x34, 0x1a, 0x55, (바이트) 0xad, (바이트) 0x93, 0x32, 0x30, (바이트) 0xf5, (바이트) 0x8c, (바이트) 0xb1, (바이트) 0xe3, 0x1d, (비트) 0xf6, (바이트) 0xe2, 0x2e, (바이트) 0x82, 0x66, (바이트) 0xca, 0x60, (비트) 0xc0, 0x29, 0x23, (바이트) 0xab, 0x0d, 0x53, 0x4e, 0x6f, (바이트) 0xd5, (바이트) 0xdb, 0x37, 0x45, (바이트) 0xde, (바이트) 0xfd, (바이트) 0x8e, 0x2f, 0x03, (바이트) 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51, (바이트) 0x8d, 0x1b, (바이트) 0xaf, (바이트) 0x92, (바이트) 0xbb, (바이트) 0xdd, (바이트) 0xbc, 0x7f, 0x11, (바이트) 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, (byte) 0xd8, 0x0a, (byte) 0xc1, 0x31, (바이트) 0x88, (바이트) 0xa5, (바이트) 0xcd, 0x7b, (바이트) 0xbd, 0x2d, 0x74, (비트) 0xd0, 0x12, (바이트) 0xb8, (바이트) 0xe5, (바이트) 0xb4, (바이트) 0xb0, (바이트) 0x89, 0x69, (바이트) 0x97, 0x4a, 0x0c, (바이트) 0x96, 0x77, 0x7e, 0x65, (바이트) 0xb9, (바이트) 0xf1, 0x09, (비트) 0xc5, 0x6e, (바이트) 0xc6, (바이트) 0x84, 0x18, (바이트) 0xf0, 0x7d, (비트) 0xec, 0x3a, (바이트) 0xdc, 0x4d, 0x20, 0x79, (바이트) 0xee, 0x5f, 0x3e, (바이트) 0xd7, (바이트) 0xcb, 0x39, 0x48 };
개인 정수[] CK = { 0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, 0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9, 0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249, 0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9, 0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229, 0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299, 0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209, 0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279 };
private int Rotl(int x, int y) { X << Y → x >>> (32 - Y); }
private int ByteSub(int A) { 반환 (Sbox[A >>> 24 & 0xFF] & 0xFF) << 24 | (Sbox[A >>> 16 & 0xFF] & 0xFF) << 16 | (Sbox[A >>> 8 & 0xFF] & 0xFF) << 8 | (Sbox[A & 0xFF] & 0xFF); }
private int L1(int B) { B 반환 ^ Rotl(B, 2) ^ Rotl(B, 10) ^ Rotl(B, 18) ^ Rotl(B, 24); return B^(B<<2|B>>>30)^(B<<10|B>>>22)^(B<<18|B>>>14)^(B<<24|B>>>8); }
private int L2(int B) { B ^ Rotl(B, 13) ^ Rotl(B, 23); return B^(B<<13|B>>>19)^(B<<23|B>>>9); }
void SMS4Crypt(byte[] 입력, 바이트[] 출력, int[] rk) { 지능 R, 미드, x0, x1, x2, x3; int[] x = 새로운 int[4]; int[] TMP = 새로운 int[4]; (정수: i = 0; 저는 4< 있습니다; i++) { tmp[0] = 입력[0 + 4 * i] & 0xff; tmp[1] = 입력[1 + 4 * i] & 0xff; tmp[2] = 입력[2 + 4 * i] & 0xff; tmp[3] = 입력[3 + 4 * i] & 0xff; x= TMP[0] << 24 | TMP[1] << 16 | TMP[2] << 8 | TMP[3]; x=(Input[0+4*i]<<24| Input[1+4*i]<<16| 입력[2+4*i]<<8| Input[3+4*i]); } (r = 0; < 32; r += 4) { 미드 = x[1] ^ x[2] ^ x[3] ^ rk[r + 0]; mid = ByteSub(mid); x[0] = x[0] ^ L1(중간); x4
미드 = x[2] ^ x[3] ^ x[0] ^ rk[r + 1]; mid = ByteSub(mid); x[1] = x[1] ^ L1(중간); x5
미드 = x[3] ^ x[0] ^ x[1] ^ rk[r + 2]; mid = ByteSub(mid); x[2] = x[2] ^ L1(중간); x6
미드 = x[0] ^ x[1] ^ x[2] ^ rk[r + 3]; mid = ByteSub(mid); x[3] = x[3] ^ L1(중간); x7 }
뒷면 (int j = 0; j < 16; j += 4) { Output[j] = (바이트) (x[3 - j / 4] >>> 24 & 0xFF); 출력[j + 1] = (바이트) (x[3 - j / 4] >>> 16 & 0xFF); 출력[j + 2] = (바이트) (x[3 - j / 4] >>> 8 & 0xFF); 출력[j + 3] = (바이트) (x[3 - j / 4] & 0xFF); } }
private void SMS4KeyExt(byte[] Key, int[] rk, int CryptFlag) { 인터셉트 레드, 미드; int[] x = 새로운 int[4]; int[] TMP = 새로운 int[4]; (정수: i = 0; 저는 4< 있습니다; i++) { tmp[0] = 키[0 + 4 * i] & 0xFF; tmp[1] = 키[1 + 4 * i] & 0xff; tmp[2] = Key[2 + 4 * i] & 0xff; tmp[3] = 키[3 + 4 * i] & 0xff; x= TMP[0] << 24 | TMP[1] << 16 | TMP[2] << 8 | TMP[3]; x=Key[0+4*i]<<24| Key[1+4*i]<<16| Key[2+4*i]<<8| Key[3+4*i]; } x[0] ^= 0xa3b1bac6; x[1] ^= 0x56aa3350; x[2] ^= 0x677d9197; x[3] ^= 0xb27022dc; (r = 0; < 32; r += 4) { 미드 = x[1] ^ x[2] ^ x[3] ^ CK[R + 0]; mid = ByteSub(mid); rk[r + 0] = x[0] ^= L2(중간); rk0=K4
미드 = x[2] ^ x[3] ^ x[0] ^ CK[R + 1]; mid = ByteSub(mid); rk[r + 1] = x[1] ^= L2(중간); rk1=K5
미드 = x[3] ^ x[0] ^ x[1] ^ CK[R + 2]; mid = ByteSub(mid); rk[r + 2] = x[2] ^= L2(중간); rk2=K6
미드 = x[0] ^ x[1] ^ x[2] ^ CK[R + 3]; mid = ByteSub(mid); rk[r + 3] = x[3] ^= L2(중간); rk3=K7 }
휠 키 순서를 복호화하기: rk31, rk30,...,rk0 만약 (CryptFlag == DECRYPT) { (r = 0; r < 16; r++) { 중간 = RK[R]; rk[r] = rk[31 - r]; RK[31 - R] = 미드; } } }
public int sms4(byte[] in, int inLen, byte[] key, byte[] out, int CryptFlag) { 정정력 포인트 = 0; int[] round_key = 새로운 int[라운드]; int[] round_key={0}; SMS4KeyExt(키, round_key, CryptFlag); 바이트[] 입력 = 새 비트[16]; 바이트[] 출력 = 새 바이트[16];
while (inLen >= 블록) { input = Arrays.copyOfRange(in, point, point + 16); output=Arrays.copyOfRange(out, point, point+16); SMS4Crypt(입력, 출력, round_key); System.arraycopy(output, 0, out, point, BLOCK); inLen -= 블록; 포인트 += 블록; }
0을 반환; }
}
패키지 외부 인터페이스:
이 기본 클래스를 기반으로 주요 인터페이스는 다음과 같습니다:
개인 정적 바이트[] encode16(byte[] plain, byte[] key); private static byte[] decode16(byte[] cipher, byte[] key); private static byte[] encode32(byte[] plain, byte[] key); private static byte[] decode32(byte[] cipher, byte[] key); 공용 정적 바이트[] encodeSMS4(byte[] plain, byte[] key); 공개 정적 바이트[] decodeSMS4(byte[] cipher, byte[] key); 공개 정적 문자열 디코딩SMS4toString(byte[] cipher, byte[] key);
encode16(byte[], byte[])는 16비트 평문과 16비트 키를 암호화하는 인터페이스입니다; 개인 정적 바이트[] decode16(byte[] 암호, byte[] 키): 16비트 암호문과 16비트 키를 복호화하는 인터페이스입니다; 개인 정적 바이트[] encode32(byte[] plain, byte[] 키): 32비트 평문과 16비트 키를 암호화하는 인터페이스입니다. 개인 정적 바이트[] decode32(byte[] 암호, byte[] 키): 32비트 암호문과 16비트 키를 복호화하는 인터페이스입니다. 공개 정적 바이트[] encodeSMS4(byte[] plain, byte[] 키): 이 인터페이스는 평문과 16비트 키를 무제한 바이트로 암호화합니다. 공개 정적 바이트[] 디코드SMS4(byte[] cipher, byte[] 키): 무제한 바이트 수를 가진 암호문 및 16비트 키를 복호화하는 인터페이스입니다. 공개 정적 문자열 디코딩SMS4toString(byte[] cipher, byte[] 키): 무제한 바이트의 암호문과 16비트 키를 복호화하는 인터페이스입니다. 인터페이스 방법 코드:
공용 정적 바이트[] encodeSMS4(문자열 평문, 바이트[] 키) { 만약 (평문 == null || plaintext.equals()) { null을 반환; } for (int i = plaintext.getBytes().길이 % 16; 저는 16< 됩니다; i++) { 평문 += ''; }
return SMS4.encodeSMS4(plaintext.getBytes(), 키); }
/** * 무제한 평문 길이의 SMS4 암호화 * * @param 평문 * @param 키 * @return */ 공개 정적 바이트[] encodeSMS4(byte[] plaintext, byte[] key) { byte[] 암호문 = new byte[plaintext.length];
정수 k = 0; int plainLen = plaintext.length; 반면 (k + 16 <= plainLen) { byte[] cellPlain = 새 byte[16]; (정수: i = 0; 저는 16< 됩니다; i++) { 셀플레인= 평문[k + i]; } byte[] cellCipher = encode16(cellPlain, key); (정수: i = 0; 저는 cellCipher.length를 <합니다; i++) { 암호문[k + i] = cellCipher; }
k += 16; }
암호문 반환; }
/** * 평문 길이 제한 없는 SMS4 복호화 * * @param 암호문 * @param 키 * @return */ 공개 정적 바이트[] decodeSMS4(byte[] 암호문, byte[] 키) { byte[] 평문 = 새로운 byte[ciphertext.length];
정수 k = 0; int cipherLen = ciphertext.length; 반면 (k + 16 <= cipherLen) { byte[] cellCipher = 새 바이트[16]; (정수: i = 0; 저는 16< 됩니다; i++) { 셀사이퍼= 암호문[k + i]; } byte[] cellPlain = decode16(cellCipher, key); (정수: i = 0; 저는 cellPlain.length를 <합니다; i++) { 평문[k + i] = cellPlain; }
k += 16; }
평문 반환; }
/** * 평문 문자열을 얻기 위해 복호화함 * @param 암호문 * @param 키 * @return */ public static string decodeSMS4toString(byte[] 암호문, byte[] key) { byte[] 평문 = 새로운 byte[ciphertext.length]; 평문 = decodeSMS4(암호문, 키); return new String(평문); }
/** * 16비트 평문만 암호화됩니다 * * @param 평문 * @param 키 * @return */ 개인 고정 바이트[] encode16(byte[] plaintext, byte[] key) { byte[] 암호 = 새로운 바이트[16]; SMS4 sm4 = 새로운 SMS4(); sm4.sms4(평문, 16, 키, 암호, 암호화);
반환 암호; }
/** * 복호화되는 것은 16비트 암호문뿐입니다 * * @param 평문 * @param 키 * @return */ 개인 정적 바이트[] decode16(byte[] 암호문, byte[] key) { byte[] plain = 새 바이트[16]; SMS4 sm4 = 새로운 SMS4(); sm4.sms4(암호문, 16, 키, 평범, 복호);
평정으로 반환; } 여기서 32비트 평문 암호화만을 소개하지는 않겠습니다. 이는 16비트 평문 방식과 매우 유사합니다.
평문 길이를 제한하지 않고 암호화 및 복호화를 위한 기본 알고리즘은 16비트만 암호화하고 복호화하는 이 기본 기반을 기반으로 합니다. 16비트 이상의 평문에는 패킷 암호화가 사용됩니다. 만약 30비트 같은 평문이 16으로 나누어질 수 없다면, 16으로 나누어질 수 있을 때까지 채우는 방법이 있습니다. 원칙적으로는 가장 작은 숫자만 16으로 나눌 수 있습니다. 물론 행복하면 더 크게 해도 상관없습니다. 왜냐하면 그것이 마지막 기호이기 때문입니다.
패킷 암호화는 각 16비트 평문을 한 번씩 암호화한 후, 암호화된 16비트 암호문을 새로운 암호문으로 재조립하는 것입니다. 복호화 과정에서 이 문서도 단일 16비트 조각으로 분리되고, 복호화된 평문들 중 여러 개가 새로운 평문으로 재조립됩니다.
사용 시연:
키 byte[] 키 = { 0x01, 0x23, 0x45, 0x67, (byte) 0x89, (byte) 0xab, (비트) 0xcd, (바이트) 0xef, (바이트) 0xfe, (바이트) 0xdc, (바이트) 0xba, (바이트) 0x98, 0x76, 0x54, 0x32, 0x10 };
문자열 newString = 코딩, 안녕하세요!; 평문
byte[] enOut = SMS4.encodeSMS4(newString, key); 만약 (enOut == null) { 돌아오다; }
System.out.println(암호화 결과:); printBit(enOut);
byte[] deOut = SMS4.decodeSMS4(enOut, key); System.out.println( 복호화 결과(반환 바이트[]) :); printBit(deOut);
String deOutStr = SMS4.decodeSMS4toString(enOut, key); System.out.println( 결과 복호화 (문자열 반환): + deOutStr); |