이번에 CSAP 인증 항목 중에서 [CSAP] 12.1.4 데이터 보호
때문에
파일 암호화 코드를 작성하게 되었습니다.
암호화 관련해서 아는 게 하나도 없다보니 간단한 코드인데도,
하루종일 걸렸네요. 아무튼 또 그러지 않기 위해서 기록합니다!
필요하신 분들도 가져가서 사용하시길 바랍니다~
package coding.toast.demo2;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class FileEncryptionUtil {
// 원하시는 Key 값이 있다면 이 KEY 변수의 값을 변경해주세요!!!!
private static final String KEY = "ThisIsASecretKey1234".substring(0,16);
private static final String IV = KEY.substring(0, 16);
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static SecretKeySpec generateKey() {
return new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
}
private static IvParameterSpec generateIvParamSpec() {
return new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));
}
public static void encryptFile(File inputFile, File outputFile) {
doCrypto(Cipher.ENCRYPT_MODE, inputFile, outputFile);
}
public static void encryptStream(InputStream inputStream, OutputStream outputStream) {
doCryptoStream(Cipher.ENCRYPT_MODE, inputStream, outputStream);
}
public static void decryptFile(File inputFile, File outputFile) {
doCrypto(Cipher.DECRYPT_MODE, inputFile, outputFile);
}
public static void decryptStream(InputStream inputStream, OutputStream outputStream) {
doCryptoStream(Cipher.DECRYPT_MODE, inputStream, outputStream);
}
private static void doCrypto(int cipherMode, File inputFile, File outputFile) {
try {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(cipherMode, generateKey(), generateIvParamSpec());
try (FileInputStream inputStream = new FileInputStream(inputFile);
FileOutputStream outputStream = new FileOutputStream(outputFile);
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) >= 0) {
cipherOutputStream.write(buffer, 0, bytesRead);
}
cipherOutputStream.flush();
} catch (IOException e) {
throw new RuntimeException("I/O error while processing the file", e);
}
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException |
InvalidAlgorithmParameterException e) {
throw new RuntimeException("Error during encryption/decryption process", e);
}
}
private static void doCryptoStream(int cipherMode, InputStream inputStream, OutputStream outputStream) {
try {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(cipherMode, generateKey(), generateIvParamSpec());
try (CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher)) {
byte[] buffer = new byte[2048];
int bytesRead;
while ((bytesRead = cipherInputStream.read(buffer)) >= 0) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
} catch (IOException e) {
throw new RuntimeException("I/O error while processing the file", e);
}
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException |
InvalidAlgorithmParameterException e) {
throw new RuntimeException("Error during encryption/decryption process", e);
} finally {
if(outputStream !=null)
try {outputStream.close();}
catch (IOException e) {System.out.println("ignore!");}
}
}
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("c:/study/some.txt");
FileOutputStream fileOutputStream = new FileOutputStream("c:/study/some_encrpyt.txt");
encryptStream(fileInputStream, fileOutputStream);
FileInputStream encryptFileIs = new FileInputStream("c:/study/some_encrpyt.txt");
FileOutputStream decryptFileOs = new FileOutputStream("c:/study/some_decrpyt.txt");
decryptStream(encryptFileIs, decryptFileOs);
}
}
package me.dailycode.crypto.impl;
// 일단 인터페이스 하나를 생성하시길!
import me.dailycode.crypto.FileEncryptionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* <h2>파일 AES(128 bit) 알고리즘 암복호화 처리 서비스 구현체</h2>
*/
@Service("fileAesEncryptionService")
public class FileAesEncryptionServiceImpl implements FileEncryptionService {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final int BUFFER_SIZE = 2048;
private final String key;
private final String iv;
public FileAesEncryptionServiceImpl(@Value("${file.aes.crypto.key:myCustomAesKey1234567890}") String key) {
this.key = key.substring(0, 16); // 16 * 8 = 128 bit Aes!
this.iv = this.key.substring(0, 16); // IV 는 무조건 길이 16 사용
}
/**
* 생성자를 통해서 초기화한 String key 값을 통해서 생성된 KeySpec
* @return this.key 문자열을 기반으로 생성된 SecretKeySpec 인스턴스
*/
private SecretKeySpec generateKey() {
return new SecretKeySpec(this.key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
}
/**
* 생성자를 통해서 초기화한 String iv 값을 통해서 생성된 IvParameterSpec
* @return this.iv 를 통해서 생성된 IvParameterSpec 인스턴스
*/
private IvParameterSpec createIvParamSpec() {
return new IvParameterSpec(this.iv.getBytes(StandardCharsets.UTF_8));
}
/**
* <h3>암호화: File To File</h3>
* @param inputFile 암호화가 필요한 파일
* @param outputFile 암호화 결과 파일
*/
@Override
public void encryptFile(File inputFile, File outputFile) {
doCryptoFile(Cipher.ENCRYPT_MODE, inputFile, outputFile);
}
/**
* <h3>복호화: File To File</h3>
* @param inputFile 복호화가 필요한 파일
* @param outputFile 복호화 결과 파일
*/
@Override
public void decryptFile(File inputFile, File outputFile) {
doCryptoFile(Cipher.DECRYPT_MODE, inputFile, outputFile);
}
/**
* <h3>암호화: Stream To Stream</h3>
* 주의! inputStream 에 read 하고, outputStream 에 write 를 모두 마치면 close 를 수행합니다!
* @param inputStream 암호화가 필요한 byte 정보를 담는 InputStream
* @param outputStream 암호화한 결과 byte 들이 write 될 outputStream
*/
@Override
public void encryptStream(InputStream inputStream, OutputStream outputStream) {
SecretKeySpec secretKey = generateKey();
IvParameterSpec ivSpec = createIvParamSpec();
doCryptoStream(Cipher.ENCRYPT_MODE, inputStream, outputStream);
}
/**
* <h3>복호화: Stream To Stream</h3>
* 주의! inputStream 에 read 하고, outputStream 에 write 를 모두 마치면 close 를 수행합니다!
* @param inputStream 복화화가 필요한 byte 정보를 담은 InputStream
* @param outputStream 복호화한 결과 byte 들이 write 될 outputStream
*/
@Override
public void decryptStream(InputStream inputStream, OutputStream outputStream) {
SecretKeySpec secretKey = generateKey();
IvParameterSpec ivSpec = createIvParamSpec();
doCryptoStream(Cipher.DECRYPT_MODE, inputStream, outputStream);
}
/**
* <h3>암/복호화 File To File 처리 내부 메소드</h3>
* @param cipherMode 암호화(Encrypt), 복호화(Decrypt) 의 분기점
* @param inputFile 암호화를 하려는 File
* @param outputFile 암호화 결과물 File
*/
private void doCryptoFile(int cipherMode, File inputFile, File outputFile) {
try {
SecretKeySpec secretKey = generateKey();
IvParameterSpec ivSpec = createIvParamSpec();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(cipherMode, secretKey, ivSpec);
try (FileInputStream inputStream = new FileInputStream(inputFile);
FileOutputStream outputStream = new FileOutputStream(outputFile);
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher)) {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) >= 0) {
cipherOutputStream.write(buffer, 0, bytesRead);
}
cipherOutputStream.flush();
} catch (IOException e) {
throw new RuntimeException("I/O error while processing the file", e);
}
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException |
InvalidAlgorithmParameterException e) {
throw new RuntimeException("Error during encryption/decryption process", e);
}
}
/**
* <h3>암/복호화 Stream To Stream 처리 내부 메소드</h3>
* 주의! inputStream 에 read 하고, outputStream 에 write 를 모두 마치면 close 를 수행합니다! 재사용 불가!
* @param cipherMode 암호화(Encrypt), 복호화(Decrypt) 의 분기점
* @param inputStream 암호화(또는 복호화)가 필요한 byte 를 담은 inputStream
* @param outputStream 복호화(또는 암호화) byte 를 write 하려는 target outputStream
*/
private void doCryptoStream(int cipherMode, InputStream inputStream, OutputStream outputStream) {
try {
SecretKeySpec secretKey = generateKey();
IvParameterSpec ivSpec = createIvParamSpec();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(cipherMode, secretKey, ivSpec);
try (CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher)) {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = cipherInputStream.read(buffer)) >= 0) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
} catch (IOException e) {
throw new RuntimeException("I/O error while processing the file", e);
}
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException |
InvalidAlgorithmParameterException e) {
throw new RuntimeException("Error during encryption/decryption process", e);
} finally {
if(outputStream != null)
try { outputStream.close();}
catch (IOException e) { log.debug("ignore Exception / message : {}", e.getMessage());}
}
}
}