RSA 파일 암호화
1 RSA 키 쌍 생성
2 공개 키로 파일을 암호화
가) RSA 키의 인코딩
① X.509를 사용하여 인코딩 사용(공개 키와 인증서를 전달할 때 사용하는 공개 형식)
② 개인 키는 PKCS#8로 인코딩
③ 공개/개인 키든 getEncoded()를 호출하면 자동으로 인코딩 됨
나) RSA 키의 디코딩
① 인코딩된 키의 스펙 객체를 생성
② KeyFactory를 사용하여 적절한 키 타입의 인스턴스를 활성화
③ 공개 키 디코딩
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(“RSA”); PublicKey publicKey = keyFactory.generatePublic(keySpec); |
④ 개인 키의 디코딩
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(“RSA”); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); |
다) 암호화된 파일 형식
① 포맷
키의 길이 |
암호화된 키 |
초기화백터(16바이트) |
암호문서 |
라) 예제
private static void createKey(String password) throws Exception { // RSA 키 쌍 생성 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.genKeyPair();
//공개키를 파일에 쓴다. byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); FileOutputStream fos = new FileOutputStream("c:\\publicKey"); fos.write(publicKeyBytes); fos.close();
// 개인 키를 암호화한 후에 파일에 쓴다. byte[] privateKeyBytes = passwordEncrypt(password.toCharArray(), keyPair.getPrivate().getEncoded()); fos = new FileOutputStream("c:\\privateKey"); fos.write(publicKeyBytes); fos.close(); }
private static byte[] passwordEncrypt(char[] password, byte[] plaintext) throws Exception { // salt 생성 byte[] salt = new byte[9]; Random random = new Random(); random.nextBytes(salt);
// PBE 키와 사이퍼 생성 PBEKeySpec keySpec = new PBEKeySpec(password); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC"); SecretKey key = keyFactory.generateSecret(keySpec); PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATIONS);
Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC"); cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
byte[] cipherText = cipher.doFinal(plaintext);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(salt); baos.write(cipherText);
return baos.toByteArray(); }
private static void encrypt(String fileInput) throws Exception { String publicKeyFileName = "c:\\publicKey"; // 공개 키가 저장된 파일로부터 keyByte의 바이트 배열을 생성한다. FileInputStream fis = new FileInputStream(publicKeyFileName); ByteArrayOutputStream baos = new ByteArrayOutputStream();
int theByte = 0; while((theByte = fis.read()) != -1) baos.writeTo(baos); fis.close();
byte[] keyBytes = baos.toByteArray(); baos.close();
// 인코딩된 키를 RSA 공개 키의 인스턴스로 바꾼다. X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(keySpec);
String fileOutput = fileInput + ENCRYPTED_FILENAME_SUFFIX; DataOutputStream output = new DataOutputStream(new FileOutputStream(fileOutput));
// RSA 공개 키를 이용하여 세션 키를 암호화할 사이퍼를 생성한다. Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 세션 키 생성 KeyGenerator rijndaelKeyGenerator = KeyGenerator.getInstance("Rijndael"); rijndaelKeyGenerator.init(256); Key rijndaelKey = rijndaelKeyGenerator.generateKey();
// RSA 사이퍼를 이용하여 세션 키를 암호화 하고 파일에 저장한다. // 키의 길이, 인코딩된 세션 키 형식이다. byte[] encodedKeyBytes = rsaCipher.doFinal(rijndaelKey.getEncoded()); output.writeInt(encodedKeyBytes.length); output.write(encodedKeyBytes);
// 초기화 벡터 SecureRandom random = new SecureRandom(); byte[] iv = new byte[16]; random.nextBytes(iv);
//IV를 파일에 쓴다 output.write(iv);
//IV와 생성한 세션 키를 이용하여 파일의 내용을 암호화한다. IvParameterSpec spec = new IvParameterSpec(iv); Cipher symmetricCipher = Cipher.getInstance("Rijndael/CBC/PKCS5Padding"); symmetricCipher.init(Cipher.ENCRYPT_MODE, rijndaelKey, spec); CipherOutputStream cos = new CipherOutputStream(output, symmetricCipher);
FileInputStream input = new FileInputStream(fileInput); theByte = 0; while((theByte = input.read()) != -1) cos.write(theByte);
input.close(); cos.close(); return; } |
3 개인 키로 복호화
가) 예제
private static byte[] passwordDecrypt(char[] password, byte[] ciphertext) throws Exception { // salt를 읽는다. 개인키는 8byte salt를 사용했다. byte[] salt = new byte[8]; ByteArrayInputStream bais = new ByteArrayInputStream(ciphertext); bais.read(salt, 0 ,8);
byte[] remainingCiphertext = new byte[ciphertext.length-8]; bais.read(remainingCiphertext, 0, ciphertext.length-8);
//PBE 사이퍼를 생성하여 세션 키를 복원한다. PBEKeySpec keySpec = new PBEKeySpec(password); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC"); SecretKey key = keyFactory.generateSecret(keySpec); PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATIONS); Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC");
// 키 복호화 cipher.init(Cipher.DECRYPT_MODE, key, paramSpec); return cipher.doFinal(remainingCiphertext); }
private static void decrypt(String password, String fileInput) throws Exception { String privateKeyFilename = "c:\\privateKey"; // 파일로부터 개인 키를 읽어들인다. FileInputStream fis = new FileInputStream(privateKeyFilename); ByteArrayOutputStream baos = new ByteArrayOutputStream();
int theByte = 0; while((theByte = fis.read()) != -1) baos.write(theByte); fis.close(); byte[] keyByte = baos.toByteArray(); baos.close();
// 암호화된 개인 키 바이트를 복원한다. keyByte = passwordDecrypt(password.toCharArray(), keyByte);
// RSA 개인 키를 복원한다. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyByte); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
// 개인 키를 이용하여 사이퍼를 생성하고 세션 키를 복호화한다 Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); DataInputStream dis = new DataInputStream(new FileInputStream(fileInput)); byte[] encryptedKeyBytes = new byte[dis.readInt()]; dis.readFully(encryptedKeyBytes);
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] rijndaelKeyByte = rsaCipher.doFinal(encryptedKeyBytes);
SecretKey rijndaelKey = new SecretKeySpec(rijndaelKeyByte, "Rijndael");
byte[] iv = new byte[16]; dis.readFully(iv); IvParameterSpec spec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("Rijndael/CBC/PKCS5padding"); cipher.init(Cipher.DECRYPT_MODE, rijndaelKey, spec); CipherInputStream cis = new CipherInputStream(dis, cipher);
FileOutputStream fos = new FileOutputStream(fileInput + DECRYPTED_FILENAME_SUFFIX);
theByte = 0; while((theByte = cis.read()) != -1) fos.write(theByte);
cis.close(); fos.close();
return; } |
나) 복호화 과정
4 세션 키 암호화를 사용해야 하는 이유
가) 해킹에 대비해, 개인 키를 저장할 때 암호화할 필요가 있다.
나) 세션 키를 사용하면 속도가 훨씬 빨라진다.
다) RSA는 특정 메시지에 대해서 매우 취약하다.