[Java] 파일 AES 암복호화 기능

식빵·2024년 3월 14일
0

Java Lab

목록 보기
20/29
post-thumbnail
post-custom-banner

이번에 CSAP 인증 항목 중에서 [CSAP] 12.1.4 데이터 보호 때문에
파일 암호화 코드를 작성하게 되었습니다.

암호화 관련해서 아는 게 하나도 없다보니 간단한 코드인데도,
하루종일 걸렸네요. 아무튼 또 그러지 않기 위해서 기록합니다!
필요하신 분들도 가져가서 사용하시길 바랍니다~


Java Util 클래스로 만들 경우

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);
	}
}




Spring Bean 으로 만들 경우

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());}
        }
    }

}




참고한 것들

profile
백엔드를 계속 배우고 있는 개발자입니다 😊
post-custom-banner

0개의 댓글