JAVA|CRC-32 계산 예제 (Polynomial 설정)

반응형

CRC란?

이미지나 실행 프로그램, 압축파일 등등. 모든 파일은 16비트 Hex값 바이트 배열(Bytes)로 변환이 가능하다.  그래서 파일 위변조나 에러 체크를 하는 방법은 원본파일과 전송된 파일을 풀어 Hex값을 비교해서 일치하는지 확인해 보면 된다.

 

눈에 보이는 프로그램이나 가벼운 프로그램은 쉽게 알아차릴 수 있지만, 용량이 크거나 파일이 위변조가 됬는지 알려면 수많은 Hex값을 비교해봐야 하는데 쉽지 않다. 이걸 간단하게 확인 할 수 있는 방법이 CRC(Cyclic Redundancy Cehck)다. Hex값들을 다항식 알고리즘을 적용하여, 이를 송신측과 수신측이 각각 검증코드를 만들어서  일치하는지 확인하면 된다.

 

CRC 종류

CRC는 결과 자리수에따라 크게 CRC-8 / CRC-16 / CRC-32 / CRC-64 로 나눠진다. (이 외에 더있음)

더보기

∥ CRC 종류별 결과 예시

CRC-8 : ex) 0xF4 (1byte)

CRC-16 : 0x31C3 (2bytes)

CRC-32 : 0xCBF43926 (4bytes)

CRC-64 : 0x6C40DF5F0B497347 (8bytes)

이중에서 개인적으로 많이 쓰는건 CRC-16과 CRC-32다.  이 중 암호화에서 더 많이 통용되는 MD5와 SHA256등은 CRC-32를 사용하는것으로 알고있다. 자세한 부분은 해시넷 위키 를 참고하면 될듯 하다.

 

 

CRC 예제

이제 각설하고, 자바(JAVA)에서 내가 사용하고 있는 CRC-32를 계산하는 예제를 정리해본다.  해당 예제는 hex값들을 메모장에 저장해놓고, 메모장을 읽어서 해당 CRC값들을 계산한다. 

 

buffer = 메모장에 저장한 hex값(payload 길이) + CRC32결과(4byte)

 

예제에는 실제 사용되는 것처럼 버퍼에 데이터를 넣고 CRC 계산 후 CRC 결과값을 버퍼 마지막 위치에 저장한다.

 

○ CRC32.java

public class CRC32 {

    public static CRC32Data Crc32 = new CRC32Data("CRC-32", 32, 0x04C11DB7L, 0, false, false, 0, 0xCBF43926L);
    public static CRC32Data Crc32Bzip2 = new CRC32Data("CRC-32/BZIP2", 32, 0x04C11DB7L, 0xFFFFFFFFL, false, false, 0xFFFFFFFFL, 0xFC891918L);
    public static CRC32Data Crc32C = new CRC32Data("CRC-32C", 32, 0x1EDC6F41L, 0xFFFFFFFFL, true, true, 0xFFFFFFFFL, 0xE3069283L);
    public static CRC32Data Crc32D = new CRC32Data("CRC-32D", 32, 0xA833982BL, 0xFFFFFFFFL, true, true, 0xFFFFFFFFL, 0x87315576L);
    public static CRC32Data Crc32Jamcrc = new CRC32Data("CRC-32/JAMCRC", 32, 0x04C11DB7L, 0xFFFFFFFFL, true, true, 0x00000000L, 0x340BC6D9L);
    public static CRC32Data Crc32Mpeg2 = new CRC32Data("CRC-32/MPEG-2", 32, 0x04C11DB7L, 0xFFFFFFFFL, false, false, 0x00000000L, 0x0376E6E7L);
    public static CRC32Data Crc32Posix = new CRC32Data("CRC-32/POSIX", 32, 0x04C11DB7L, 0x00000000L, false, false, 0xFFFFFFFFL, 0x765E7680L);
    public static CRC32Data Crc32Q = new CRC32Data("CRC-32Q", 32, 0x814141ABL, 0x00000000L, false, false, 0x00000000L, 0x3010BF7FL);
    public static CRC32Data Crc32Xfer = new CRC32Data("CRC-32/XFER", 32, 0x000000AFL, 0x00000000L, false, false, 0x00000000L, 0xBD0BE338L);

    public static final CRC32Data[] Params = new CRC32Data[]{
            Crc32, Crc32Bzip2, Crc32C, Crc32D, Crc32Jamcrc, Crc32Mpeg2, Crc32Posix, Crc32Q, Crc32Xfer
    };
}

 

 

 CRC32Data.java

public class CRC32Data {

	public CRC32Data(String name, int hashSize, long poly, long init,
			boolean refIn, boolean refOut, long xorOut, long check) {
		
		Name = name;
		HashSize = hashSize;
		Poly = poly;
		Init = init;
		RefIn = refIn;
		RefOut = refOut;
		XorOut = xorOut;
		Check = check;
		
	}

	public long Check;
	public int HashSize;
	public long Init;
	public String Name;
	public long Poly;
	public boolean RefIn;
	public boolean RefOut;
	public long XorOut;
}

 

 

CrcCalculator.java

public class CrcCalculator {

    public CRC32Data Parameters;
    public byte HashSize = 8;
    private long _mask = 0x0000000000000000L;
    private long[] _table = new long[256];
    
    public CrcCalculator(CRC32Data params){
        Parameters = params;

        HashSize = (byte) params.HashSize;
        if (HashSize < 64){
            _mask = (1L << HashSize) - 1;
        }

        CreateTable();
    }

    public long Calc(byte[] data, int offset, int length){
        long init = Parameters.RefOut ? ReverseBits(Parameters.Init, HashSize) : Parameters.Init;
        long hash = ComputeCrc(init, data, offset, length);
        return (hash ^ Parameters.XorOut) & _mask;
    }

    private long ComputeCrc(long init, byte[] data, int offset, int length){
    	
        long crc = init;

        if (Parameters.RefOut){
            for (int i = offset; i < offset + length; i++){
                crc = (_table[(int)((crc ^ data[i]) & 0xFF)] ^ (crc >>> 8));
                crc &= _mask;
            }
        }else{
            int toRight = (HashSize - 8);
            toRight = toRight < 0 ? 0 : toRight;
            for (int i = offset; i < offset + length; i++){
                crc = (_table[(int)(((crc >> toRight) ^ data[i]) & 0xFF)] ^ (crc << 8));
                crc &= _mask;
            }
        }

        return crc;
    }
    

    private void CreateTable(){
        for (int i = 0; i < _table.length; i++)
            _table[i] = CreateTableEntry(i);
    }

    private long CreateTableEntry(int index){
        long r = (long)index;

        if (Parameters.RefIn)
            r = ReverseBits(r, HashSize);
        else if (HashSize > 8)
            r <<= (HashSize - 8);

        long lastBit = (1L << (HashSize - 1));

        for (int i = 0; i < 8; i++){
            if ((r & lastBit) != 0)
                r = ((r << 1) ^ Parameters.Poly);
            else
                r <<= 1;
        }

        if (Parameters.RefOut)
            r = ReverseBits(r, HashSize);

        return r & _mask;
    }
    
    static long ReverseBits(long ul, int valueLength){
    	
        long newValue = 0;

        for (int i = valueLength - 1; i >= 0; i--){
        	
            newValue |= (ul & 1) << i;
            ul >>= 1;
            
        }

        return newValue;
    }

 

 CrcTest.java

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

public class CrcTest {

	private static int bufferlength = [각자 정의];

	public static void main(String[] args) throws Exception {

		readTxt();
	}

	public static void readTxt() throws Exception {

		// payload + CRC 4byte 포함하여 사이즈 정의
		byte[] buffer = new byte[bufferlength];

		// txt 파일에 hex값 정의 후 읽어온다.
		byte[] payload = readFileToByteArray("./db/test.txt");
		// 읽은값을 버퍼에 저장
		System.arraycopy(payload, 0, buffer, 0, payload.length);

		// CRC32 기본 알고리즘 적용
		CrcCalculator crcCal = new CrcCalculator(CRC32.Params[0]);
		// CRC결과값 4byte 제외한 데이터까지 계산
		long nInPacketCrc32 = crcCal.Calc(buffer, 0, bufferlength - 4);
		System.out.println("nInPacketCrc32 : " + nInPacketCrc32);

		// 4byte 0채움
		String getHex = Long.toHexString(nInPacketCrc32);
		if (getHex.length() == 7) {
			getHex = "0".concat(getHex);
		} else if (getHex.length() == 6) {
			getHex = "00".concat(getHex);
		} else if (getHex.length() == 5) {
			getHex = "000".concat(getHex);
		}

		// 만들어진 CRC결과 버퍼에저장.
		byte[] hexCrc32 = new byte[4];
		hexCrc32 = hexStringToByteArray(getHex);
		System.arraycopy(hexCrc32, 0, buffer, 0, hexCrc32.length);
	}

	private static byte[] readFileToByteArray(String fileName) throws Exception {

		File file = new File(fileName);
		BufferedReader br = new BufferedReader(new FileReader(file));
		String str = br.readLine(); // 변수 선언과 첫번째 문자열 대입
		StringBuffer sb = new StringBuffer();
		while (str != null) {
			str = str.replace(" ", "").replace("\n", "");
			sb.append(str);
			str = br.readLine(); // 메모장 다음 문자열 읽기
		}
		br.close();

		return hexStringToByteArray(sb.toString());
	}

	public static byte[] hexStringToByteArray(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
		}
		return data;
	}
}

 

 

CRC Polynomial 설정

CRC 예제는 위와 같다. 여기서 Polynomial은 CRC32.java 에서 설정하면 된다. 

public static CRC32Data Crc32 = new CRC32Data("CRC-32", 32, 0x04C11DB7L, 0, false, false, 0, 0xCBF43926L);

여기서 0x04C11DB7L이 Polynomial 값이다. 뒤에 L은 데이터 타입 Long을 의미하는듯 하다. 

 

뒤에 0,false, false,0 은 각각 Initial Value , Input reflected, Result reflected, Xor Value 등등인데 이 부분을 어떻게 건드리냐에 따라 CRC32 결과값이 달라진다.

 

이 부분은 개발자입장에서는 문서나 규격에 정의된대로 설정하면 되는데, 미리 잘 만들어진 CRC 예제 사이트를 참고해서 원하는 결과값이 나오는지 확인하고 맞춰가면 될듯하다.

 

 

반응형