오늘은 어디로 갈까...

MD5와 SHA1 본문

낙서

MD5와 SHA1

剛宇 2009. 3. 9. 13:33
 오늘은 MD5와 SHA1에 대해서 간단히 알아보기로 하자.
 MD5(Message-Digest algorithm 5)는 말그대로 메시지 축약 알고리즘으로서 128비트의 해쉬를 제공한다. RFC-1321에 정의되어 있으며, 현재는 파일 무결성 검사용으로 많이 쓰이고 있다. 보안 관련 용도로 사용하기도 했지만, 현재 암호화 결함이 별견되어서 SHA1같은 다른 알고리즘을 사용하는것을 권장하고 있다. (MD5의 결함을 이용해서 SSL 인증서를 변조가능하다는것이, 2008년 12월에 발표되기도 했다. http://www.win.tue.nl/hashclash/rogue-ca/)

 SHA(Secure Hash Standard)는 암호학적 해쉬 함수들을 모아놓은것으로서 SHA-0, SHA-1, SHA-2(SHA-224, SHA-256, SHA-384, SHA-512)등 있다. SHA-1은 160비트의 해쉬를 제공하고, 보안 관련 용도로 많이 사용되고 있다.

 자바에서는 MessageDigest 클래스를 이용하면 MD5, SHA1을 손쉽게 사용할 수 있다.
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import kr.kangwoo.util.ByteUtils;

public class HashTest {
	public static void main(String[] argv) throws NoSuchAlgorithmException {
		System.out.println(ByteUtils.toHexString(md5("message digest")));
		System.out.println(ByteUtils.toHexString(sha1("message digest")));
	}

	public static byte[] md5(String input) throws NoSuchAlgorithmException {
		MessageDigest messageDigest = MessageDigest.getInstance("MD5");
		return messageDigest.digest(input.getBytes());
	}
	
	public static byte[] sha1(String input) throws NoSuchAlgorithmException {
		MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
		return messageDigest.digest(input.getBytes());
	}
}
* 실행 결과
f96b697d7cb7938d525a2f31aaf161d0
c12252ceda8be8994d5fa0290a47231c1d16aae3

 이처럼 MessageDigest.getInstance("알고리즘") 메소드를 이용해서 간단하게 사용할 수 있다.
 보안쪽은 생략하고 파일 검증을 한번 해보도록 하자.
http://commons.apache.org/downloads/download_logging.cgi
위 사이트에 접속해보자. Logging 라이브러리를 다운로드 받는곳인데, 다운로드 링크 아래에 보면 [md5]라고 있다.
여기서는 Source에 있는 1.1.1.zip을 다운받아보도록 하겠다. (여기서는 C:\에 저장했다.) 그리고 [md5]를 클릭해보면
MD5 (commons-logging-1.1.1-src.zip) = 68fb2d8328ca6bb77f2fa62a26b81c2e
란 정보를 볼 수 있을것이다. 즉 다운받은 파일의 md5 해쉬값이 68fb2d8328ca6bb77f2fa62a26b81c2e 이라는것이다. 우리가 받은 파일이 정말 저 파일인지 한번 검증해볼까?
	public static void main(String[] argv) throws NoSuchAlgorithmException {
		System.out.println(ByteUtils.toHexString(md5(new File("c:/commons-logging-1.1.1-src.zip"))));
	}

    public static byte[] md5(File file) throws IOException, NoSuchAlgorithmException {
    	BufferedInputStream bis = null;
		try {
			bis = new BufferedInputStream(new FileInputStream(file));
			MessageDigest md = MessageDigest.getInstance("MD5");
			int read = -1;
			byte[] buffer = new byte[1024];
			while ((read = bis.read(buffer)) != -1) {
				md.update(buffer, 0, read);
			}
			return md.digest();
		} finally {
			if (bis != null) try { bis.close(); } catch(IOException ie) {}
		}
    }

* 실행 결과
68fb2d8328ca6bb77f2fa62a26b81c2e
정상적인(?) 파일이라면 실행 결과처럼 68fb2d8328ca6bb77f2fa62a26b81c2e 란 값이 출력될것이다. 위의 소스를 DigestInputStream를 이용해서 아래처럼 살짝 바꿀 수도 있다.
	public static void main(String[] argv) throws NoSuchAlgorithmException {
		System.out.println(ByteUtils.toHexString(md5(new File("c:/commons-logging-1.1.1-src.zip"))));
	}

    public static byte[] md5(File file) throws IOException, NoSuchAlgorithmException {
    	DigestInputStream  bis = null;
		try {
			bis = new DigestInputStream(new BufferedInputStream(new FileInputStream(file)), MessageDigest.getInstance("MD5"));
			byte[] buffer = new byte[1024];
			while (bis.read(buffer) != -1) {
				// nothing
			}
			MessageDigest md = bis.getMessageDigest();
			return md.digest();
		} finally {
			if (bis != null) try { bis.close(); } catch(IOException ie) {}
		}
    }

필자같은 경우는 이 MD5를 파일 중복 업로드 체크에 이용하기도 한다. 동일한 파일인지 비교할때, 내용을 일일이 비교하면 상당히 큰 자원을 소비하게되는데, MD5 값을 계산해서 그 값만 비교하면 아주 간단히 같은것인지 판단할 수 있다. 물론 해쉬 충돌의 위험성도 존재하기는 하겠지만, 파일 크기등 약간의 조미료를 가하면, 무시 가능할 수준이라고 본다.(믿거나 말거나)