[암호화] 암호화 방식 분류 및 자바 예제

개발개발·2022년 6월 15일
0
post-thumbnail

암호화 기술은 블록체인을 가능하게 하는 핵심 기술이다. 블록체인 네트워크를 이해하기 위해서 암호화에 대해서 공부한 내용들을 정리한다.

암호화의 분류 기준

  • 복호화 가능 여부
  • 암복호화 키의 대칭성
  1. 복호화 가능 여부
    복호화가 안되면 단방향 암호화, 복호화가 가능하면 양방향 암호화다.

1) 단방향 암호화
평문을 단방향 암호화시키면 원형을 알아볼 수 없게 변형된다. 하지만 평문이 같다면 암호화된 내용도 같다.

사용되는 곳

  • DB에 개인정보를 보관할 때
  • 작업증명에서 hash값을 구할 때

자바 예제

import java.security.MessageDigest;

import org.bouncycastle.util.encoders.Hex;

public class OneWayCrypt {

	public static String Digest(String plain) throws Exception{
		MessageDigest md = MessageDigest.getInstance("SHA-256");
		md.update(plain.getBytes());
		byte[] b = md.digest();
		return Hex.toHexString(b);
	}
}
public class Main

	public static void main(String[] args) throws Exception{
		
		String plain1 = "1234";
        String plain2 = "1230";
        
        // 평문이 같다면 암호화된 내용도 동일하다. -> 흔히 사용되는 평문을 모아놓은 레인보우 테이블이 있다.
        System.out.println(OneWayCrypt.Digest(plain1));
        System.out.println(OneWayCrypt.Digest(plain2));
        System.out.println(OneWayCrypt.Digest(plain2));
		
	}
}

2) 양방향 암호화
(1) 대칭키 암호화
복호화할때 사용하는 키는 암호화할때 사용한 키와 같다.
자바 예제

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
	// 암호화알고리즘/운용 모드/ 패딩
    // 아래 포스팅에 cipher에 사용되는 arg들에 대해 상세하게 설명되어 있다.
    // http://happinessoncode.com/2019/04/06/java-cipher-algorithm-mode-padding/
	private String arg = "AES/CBC/PKCS5Padding";
	
	private Cipher cipher;
	private SecretKeySpec keySpec;
	private IvParameterSpec ivParameterSpec;
	
	public AES(String key) throws NoSuchAlgorithmException, NoSuchPaddingException {
		byte[] specBytes = new byte[16];
		byte[] keyBytes = key.getBytes();
		int len = keyBytes.length;
		
        if (len > specBytes.length) {
            len = specBytes.length;
        }
		System.arraycopy(keyBytes, 0, specBytes, 0, len);
		
		this.cipher=Cipher.getInstance(arg);		
		this.keySpec=new SecretKeySpec(specBytes, "AES");
		this.ivParameterSpec=new IvParameterSpec(specBytes);
	}
	public String encrypt(String plain) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
		cipher.init(Cipher.ENCRYPT_MODE, keySpec,ivParameterSpec);
		
		byte[] encrypted = cipher.doFinal(plain.getBytes());
		return Base64.getEncoder().encodeToString(encrypted);
		
	}
	
	public String decrypt(String encoded) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
		cipher.init(Cipher.DECRYPT_MODE, keySpec,ivParameterSpec);
		
		byte[] base64Decoded = Base64.getDecoder().decode(encoded);
		byte[] decrypted = cipher.doFinal(base64Decoded);
		
		return new String(decrypted);
	}
}

아래는 main에서 테스트한 내용이다.

import crypto.AES;

public class Main

	public static void main(String[] args) throws Exception{
		
		String key1 ="a45hvn";
		String key2 ="testkey2";
		AES aes1 = new AES(key1);
		AES aes2 = new AES(key2);
		
		String plain = "plain text!";
		
		String encAes1 = aes1.encrypt(plain);
		String encAes2 = aes2.encrypt(plain);
		
		System.out.println("암호화 테스트");
		System.out.println("aes1 : "+ encAes1);
		System.out.println("aes2 : "+ encAes2);
		
		System.out.println("복호화 테스트");
		System.out.println("enc by aes1 -> dec by aes1 : "+aes1.decrypt(encAes1));
		System.out.println("enc by aes1 -> dec by aes2 : "+aes1.decrypt(encAes2));
		
	}
}

같은 평문이지만 키가 다르면 암호화되는 내용이 다르다.
다른 키로 복호화를 시도하면 BadPaddingException이 발생한다.

(2) 공개키 암호화
암호할때 사용한 키가 복호화할때 사용하는 키와 다르다.

자바 예제
공개키를 생성하는 ECDSA 클래스, 서명과 검증역할을 하는 SignUtil 클래스를 만들고 main메소드에서 테스트를 진행해봤다.

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class ECDSA {

	// 지갑과 비슷한 역할을 한다.
	private PrivateKey privateKey;
	private PublicKey publicKey;

	private byte[] privatekey;
	private byte[] publickey;

	public ECDSA(){
    	// KeyPairGenerateor에서 instance를 생성하기 전에 추가해줘야 한다.
		Security.addProvider(new BouncyCastleProvider());
		try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA","BC");
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1");

            keyPairGenerator.initialize(ecSpec,random);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();

            privateKey = keyPair.getPrivate();
            publicKey = keyPair.getPublic();

            this.privatekey = privateKey.getEncoded();
            this.publickey = publicKey.getEncoded();
        }catch (Exception e){
            throw new RuntimeException(e);
        }
	}
	
	public ECDSA(byte[] privateKey,byte[] publicKey) {
		Security.addProvider(new BouncyCastleProvider());
		try {
            KeyFactory keyFactory = KeyFactory.getInstance("ECDSA","BC");
            // byte array를 PrivateKey로 변경
            this.privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey));
            // byte array를 PublicKey 변경
            this.publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));
        } catch (Exception e) {
            e.printStackTrace();
        }
	}

	// 이하 getter setter 생략...
    
	
	
}
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;

public class SignUtil {

	public static byte[] applyECDSASig(PrivateKey privateKey,String input){
        Signature dsa;
        byte[] output = new byte[0];
        try {
            dsa = Signature.getInstance("ECDSA","BC");
            dsa.initSign(privateKey);
            byte[] strByte = input.getBytes();
            dsa.update(strByte);
            byte[] realSig = dsa.sign();
            output = realSig;

        }catch (Exception e){
            throw new RuntimeException(e);
        }
        return output;
    }

    public static boolean verifyECDSASig(PublicKey publicKey,String data, byte[] signature){
        try {
            Signature ecdsaVerify= Signature.getInstance("ECDSA","BC");
            ecdsaVerify.initVerify(publicKey);
            ecdsaVerify.update(data.getBytes());
            return ecdsaVerify.verify(signature);
        } catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}
import org.bouncycastle.util.encoders.Hex;
import ECDSA;
import SignUtil;

public class Main {

	public static void main(String[] args) throws Exception{
		
		ECDSA ec1 = new ECDSA();
		ECDSA ec2 = new ECDSA();
		
		System.out.println("서명 검증하기");
		byte[] ec1_sign = SignUtil.applyECDSASig(ec1.getPrivateKey(), "text");// ec1의 개인키로 서명, 이때 text라는 문장을 이용
		
		System.out.println(SignUtil.verifyECDSASig(ec1.getPublicKey(), "text", ec1_sign)); // 공개키와 일치, 서명할때 사용한 문장 일치 -> false
		System.out.println(SignUtil.verifyECDSASig(ec1.getPublicKey(), "wrong text", ec1_sign));// 공개키와 일치, 서명할때 사용한 문장 불일치 -> false
		System.out.println(SignUtil.verifyECDSASig(ec2.getPublicKey(), "text", ec1_sign));// 공개키와 불일치, 서명한 문장 일치 -> false
		
		// byte array를 hex string으로 변환
        // 파일로 저장해서 보관할 수 있다.
		String ec1_privatekey_hex = Hex.toHexString(ec1.getPrivatekey());
		String ec1_publickey_hex= Hex.toHexString(ec1.getPublickey());
		
        System.out.println();
		System.out.println("key to hex string");
		System.out.println("private key : 0x"+ec1_privatekey_hex);
		System.out.println("public key : 0x"+ec1_publickey_hex);
		
		System.out.println();
		System.out.println("byte array를 통해 key 복구");
        // byte array로 저장된 내용을 다시 Key로 복구
		ECDSA ec1_remake = new ECDSA(Hex.decodeStrict(ec1_privatekey_hex), Hex.decode(ec1_publickey_hex));
		System.out.println(SignUtil.verifyECDSASig(ec1_remake.getPublicKey(), "text", ec1_sign)); //
		System.out.println(SignUtil.verifyECDSASig(ec1_remake.getPublicKey(), "wrong text", ec1_sign)); //
		 
	}
}

위와같은 방식으로 신원증명할때 사용할 수 있다.


profile
청포도루이보스민트티

0개의 댓글