IT 강좌(IT Lectures)/Java

12강. 보안과 암호화

소울입니다 2024. 6. 29. 08:30
728x90
반응형

 

챕터 12: 보안과 암호화


Java는 강력한 보안 기능을 제공하여 애플리케이션의 데이터와 통신을 보호할 수 있습니다. 이 장에서는 보안의 기본 개념과 Java 보안 모델, 주요 보안 위협과 그 대응 방법, 암호화 기법, 그리고 인증과 권한 관리에 대해 다룹니다.

12.1 보안 개요

보안은 데이터를 보호하고, 시스템의 무결성과 기밀성을 유지하는 중요한 개념입니다. Java는 다양한 보안 메커니즘을 제공하여 애플리케이션의 보안을 강화합니다.

 

12.1.1 보안의 기본 개념과 Java 보안 모델

  • 기밀성(Confidentiality): 데이터가 승인된 사용자에게만 접근 가능하도록 합니다.
  • 무결성(Integrity): 데이터가 인가되지 않은 변경 없이 유지되도록 합니다.
  • 가용성(Availability): 승인된 사용자가 필요할 때 데이터를 사용할 수 있도록 합니다.
  • 인증(Authentication): 사용자의 신원을 확인합니다.
  • 권한 관리(Authorization): 사용자의 접근 권한을 제어합니다.

Java 보안 모델은 다음과 같은 요소로 구성됩니다:

  • 클래스 로더(Class Loader): 애플리케이션 코드와 라이브러리를 로드합니다.
  • 보안 관리자(Security Manager): 애플리케이션의 실행을 제어하고, 보안 정책을 적용합니다.
  • 정책 파일(Policy File): 애플리케이션의 권한을 정의합니다.

예제: 보안 관리자 설정

public class SecurityManagerExample {
    public static void main(String[] args) {
        // 보안 관리자 설정
        System.setSecurityManager(new SecurityManager());

        // 예외 발생: 보안 관리자가 파일 삭제를 허용하지 않음
        try {
            System.out.println("Trying to delete a file...");
            java.io.File file = new java.io.File("test.txt");
            file.delete();
        } catch (SecurityException e) {
            System.out.println("SecurityException caught: " + e.getMessage());
        }
    }
}
 

설명:

  • SecurityManager: Java 애플리케이션의 보안 정책을 적용하는 클래스입니다.
  • System.setSecurityManager: 보안 관리자를 설정합니다.
  • SecurityException: 보안 정책 위반 시 발생하는 예외입니다.
  • 위 코드에서는 보안 관리자가 설정된 상태에서 파일을 삭제하려고 시도할 때 예외가 발생합니다.

추가 예제: 정책 파일 사용

 

security.policy 파일:

grant {
    permission java.io.FilePermission "<<ALL FILES>>", "read,write,delete";
};
 

예제: 정책 파일 설정

import java.security.AccessControlException;

public class PolicyFileExample {
    public static void main(String[] args) {
        System.setProperty("java.security.policy", "security.policy");
        System.setSecurityManager(new SecurityManager());

        try {
            java.io.File file = new java.io.File("test.txt");
            file.createNewFile();
            System.out.println("File created successfully");

            file.delete();
            System.out.println("File deleted successfully");
        } catch (AccessControlException e) {
            System.out.println("AccessControlException: " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 

설명:

  • 정책 파일(security.policy): 특정 권한을 설정하는 파일입니다.
  • System.setProperty("java.security.policy", "security.policy"): 정책 파일 경로를 설정합니다.
  • AccessControlException: 권한이 없을 때 발생하는 예외입니다.

12.1.2 주요 보안 위협과 그 대응 방법

주요 보안 위협에는 다음과 같은 것들이 있습니다:

  • 악성 코드(Malware): 시스템을 손상시키거나 데이터를 훔치려는 프로그램입니다.
  • SQL 인젝션(SQL Injection): 악의적인 SQL 코드를 주입하여 데이터베이스를 공격합니다.
  • 크로스 사이트 스크립팅(XSS): 웹 애플리케이션의 취약점을 이용하여 악의적인 스크립트를 삽입합니다.

대응 방법:

  • 입력 검증(Input Validation): 사용자 입력을 철저히 검증하여 악의적인 데이터를 차단합니다.
  • 준비된 문(Prepared Statements): SQL 인젝션 공격을 방지하기 위해 사용합니다.
  • 출력 인코딩(Output Encoding): 출력 데이터를 안전하게 인코딩하여 XSS 공격을 방지합니다.

예제: SQL 인젝션 방지

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class SqlInjectionPreventionExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/mydatabase";
        String username = "root";
        String password = "password";

        try (Connection conn = DriverManager.getConnection(url, username, password)) {
            // 준비된 문 사용
            String query = "SELECT * FROM users WHERE username = ? AND password = ?";
            PreparedStatement pstmt = conn.prepareStatement(query);
            pstmt.setString(1, "admin");
            pstmt.setString(2, "admin123");

            ResultSet rs = pstmt.executeQuery();
            while (rs.next()) {
                System.out.println("User: " + rs.getString("username"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
 

설명:

  • 준비된 문(Prepared Statement): SQL 인젝션 공격을 방지하기 위해 사용됩니다.
  • setString: SQL 쿼리에 변수를 안전하게 바인딩합니다.
  • 위 코드에서는 사용자 입력을 PreparedStatement로 처리하여 SQL 인젝션 공격을 방지합니다.

추가 예제: XSS 방지

import org.owasp.encoder.Encode;

public class XSSPreventionExample {
    public static void main(String[] args) {
        String userInput = "<script>alert('XSS');</script>";

        // 출력 인코딩
        String safeOutput = Encode.forHtml(userInput);
        System.out.println("Safe output: " + safeOutput);
    }
}
 

설명:

  • OWASP Encoder 라이브러리: 출력 데이터를 안전하게 인코딩하여 XSS 공격을 방지합니다.
  • Encode.forHtml: HTML 인코딩을 수행하여 XSS 공격을 방지합니다.

12.2 암호화 기법

암호화는 데이터를 보호하기 위해 원본 데이터를 암호화된 형식으로 변환하는 과정입니다. 암호화는 대칭 암호화와 비대칭 암호화로 나뉩니다.

 

12.2.1 대칭 암호화와 비대칭 암호화

  • 대칭 암호화(Symmetric Encryption): 동일한 키를 사용하여 데이터를 암호화하고 복호화합니다. 예: AES, DES
  • 비대칭 암호화(Asymmetric Encryption): 공개 키와 개인 키를 사용하여 데이터를 암호화하고 복호화합니다. 예: RSA

예제: 대칭 암호화

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.util.Base64;

public class SymmetricEncryptionExample {
    public static void main(String[] args) throws Exception {
        String plainText = "Hello, World!";
        
        // 키 생성
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(128);
        SecretKey secretKey = keyGen.generateKey();
        
        // 암호화
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
        String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
        System.out.println("Encrypted Text: " + encryptedText);
        
        // 복호화
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
        String decryptedText = new String(decryptedBytes);
        System.out.println("Decrypted Text: " + decryptedText);
    }
}
 

설명:

  • KeyGenerator: 대칭 키를 생성하는 클래스입니다.
  • Cipher: 암호화 및 복호화 작업을 수행하는 클래스입니다.
  • AES: 대칭 암호화 알고리즘 중 하나입니다.
  • 위 코드에서는 AES 알고리즘을 사용하여 문자열을 암호화하고 복호화합니다.

추가 예제: AES 암호화 및 복호화

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AESExample {
    public static void main(String[] args) throws Exception {
        String plainText = "This is a secret message";

        // 키 생성
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(128);
        SecretKey secretKey = keyGen.generateKey();
        byte[] encodedKey = secretKey.getEncoded();
        String encodedKeyString = Base64.getEncoder().encodeToString(encodedKey);

        System.out.println("Generated AES Key: " + encodedKeyString);

        // 암호화
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
        String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
        System.out.println("Encrypted Text: " + encryptedText);

        // 복호화
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
        String decryptedText = new String(decryptedBytes);
        System.out.println("Decrypted Text: " + decryptedText);
    }
}
 

예제: 비대칭 암호화

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.Cipher;
import java.util.Base64;

public class AsymmetricEncryptionExample {
    public static void main(String[] args) throws Exception {
        String plainText = "Hello, World!";
        
        // 키 쌍 생성
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048);
        KeyPair keyPair = keyGen.generateKeyPair();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        
        // 암호화
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
        String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
        System.out.println("Encrypted Text: " + encryptedText);
        
        // 복호화
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
        String decryptedText = new String(decryptedBytes);
        System.out.println("Decrypted Text: " + decryptedText);
    }
}
 

설명:

  • KeyPairGenerator: 공개 키와 개인 키 쌍을 생성하는 클래스입니다.
  • Cipher: 암호화 및 복호화 작업을 수행하는 클래스입니다.
  • RSA: 비대칭 암호화 알고리즘 중 하나입니다.
  • 위 코드에서는 RSA 알고리즘을 사용하여 문자열을 암호화하고 복호화합니다.

추가 예제: RSA 암호화 및 복호화

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import java.util.Base64;

public class RSAExample {
    public static void main(String[] args) throws Exception {
        String plainText = "This is a secret message";

        // 키 쌍 생성
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048);
        KeyPair keyPair = keyGen.generateKeyPair();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();

        // 키를 Base64로 인코딩하여 출력
        String encodedPublicKey = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        String encodedPrivateKey = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        System.out.println("Public Key: " + encodedPublicKey);
        System.out.println("Private Key: " + encodedPrivateKey);

        // 암호화
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
        String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
        System.out.println("Encrypted Text: " + encryptedText);

        // 복호화
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
        String decryptedText = new String(decryptedBytes);
        System.out.println("Decrypted Text: " + decryptedText);
    }
}
 

12.2.2 Java Cryptography Architecture (JCA)와 Java Cryptography Extension (JCE)

  • JCA(Java Cryptography Architecture): Java 플랫폼에서 암호화 작업을 수행하기 위한 프레임워크입니다. 다양한 알고리즘과 서비스 제공자를 지원합니다.
  • JCE(Java Cryptography Extension): JCA의 확장으로, 강력한 암호화 알고리즘과 키 크기를 제공합니다.

예제: JCA와 JCE 사용

import java.security.MessageDigest;

public class HashExample {
    public static void main(String[] args) throws Exception {
        String message = "Hello, World!";
        
        // SHA-256 해시 생성
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(message.getBytes());
        
        // 해시를 16진수 문자열로 변환
        StringBuilder sb = new StringBuilder();
        for (byte b : hash) {
            sb.append(String.format("%02x", b));
        }
        String hashString = sb.toString();
        
        System.out.println("SHA-256 Hash: " + hashString);
    }
}
 

설명:

  • MessageDigest: 해시 알고리즘을 구현하는 클래스입니다.
  • SHA-256: 강력한 해시 알고리즘 중 하나입니다.
  • 위 코드에서는 SHA-256 알고리즘을 사용하여 문자열의 해시 값을 생성합니다.

추가 예제: MD5 해시 생성

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5HashExample {
    public static void main(String[] args) {
        try {
            String input = "password";
            
            // MD5 해시 생성
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] hashBytes = md.digest(input.getBytes());
            
            // 해시를 16진수 문자열로 변환
            StringBuilder sb = new StringBuilder();
            for (byte b : hashBytes) {
                sb.append(String.format("%02x", b));
            }
            String hashString = sb.toString();
            
            System.out.println("MD5 Hash: " + hashString);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}
 

설명:

  • MD5: 비교적 덜 안전한 해시 알고리즘으로, 최신 애플리케이션에서는 주로 SHA-256 이상의 알고리즘을 권장합니다.
  • 위 코드에서는 MD5 알고리즘을 사용하여 문자열의 해시 값을 생성합니다.

12.3 인증과 권한 관리

인증과 권한 관리는 시스템의 보안을 유지하는 데 필수적인 요소입니다.

 

12.3.1 사용자 인증 기법

사용자 인증은 사용자의 신원을 확인하는 과정입니다. 일반적으로 사용자 이름과 비밀번호를 사용하여 인증합니다.

 

예제: 간단한 사용자 인증

import java.util.HashMap;
import java.util.Map;

public class SimpleAuthentication {
    private static Map<String, String> userDatabase = new HashMap<>();

    static {
        userDatabase.put("admin", "admin123");
        userDatabase.put("user", "user123");
    }

    public static boolean authenticate(String username, String password) {
        String storedPassword = userDatabase.get(username);
        return storedPassword != null && storedPassword.equals(password);
    }

    public static void main(String[] args) {
        System.out.println("Authentication for admin: " + authenticate("admin", "admin123"));
        System.out.println("Authentication for user: " + authenticate("user", "wrongpassword"));
    }
}
 

설명:

  • userDatabase: 사용자 이름과 비밀번호를 저장하는 간단한 데이터베이스입니다.
  • authenticate: 사용자 이름과 비밀번호를 확인하여 인증을 수행하는 메서드입니다.
  • 위 코드에서는 사용자 이름과 비밀번호를 사용하여 간단한 인증을 수행합니다.

추가 예제: 해시된 비밀번호를 사용한 인증

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

public class HashedPasswordAuthentication {
    private static Map<String, String> userDatabase = new HashMap<>();

    static {
        // 해시된 비밀번호 저장
        userDatabase.put("admin", hashPassword("admin123"));
        userDatabase.put("user", hashPassword("user123"));
    }

    public static boolean authenticate(String username, String password) {
        String storedHashedPassword = userDatabase.get(username);
        String hashedPassword = hashPassword(password);
        return storedHashedPassword != null && storedHashedPassword.equals(hashedPassword);
    }

    private static String hashPassword(String password) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] hashBytes = md.digest(password.getBytes());
            StringBuilder sb = new StringBuilder();
            for (byte b : hashBytes) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        System.out.println("Authentication for admin: " + authenticate("admin", "admin123"));
        System.out.println("Authentication for user: " + authenticate("user", "wrongpassword"));
    }
}
 

설명:

  • 해시된 비밀번호: 비밀번호를 해시하여 저장하고, 인증 시 입력된 비밀번호를 해시하여 비교합니다.
  • hashPassword: 비밀번호를 SHA-256 해시 알고리즘을 사용하여 해시합니다.

12.3.2 JAAS(Java Authentication and Authorization Service)

JAAS는 Java 애플리케이션에서 인증과 권한 관리를 제공하는 프레임워크입니다. JAAS를 사용하면 다양한 인증 기법과 권한 관리를 구현할 수 있습니다.

 

예제: JAAS 사용

import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.callback.*;

public class JAASExample {
    public static void main(String[] args) {
        try {
            // LoginContext 생성
            LoginContext lc = new LoginContext("SampleLoginModule", new MyCallbackHandler());
            // 인증 시도
            lc.login();
            // 인증 성공
            Subject subject = lc.getSubject();
            System.out.println("Authentication successful for subject: " + subject);
        } catch (LoginException e) {
            // 인증 실패
            System.out.println("Authentication failed: " + e.getMessage());
        }
    }

    static class MyCallbackHandler implements CallbackHandler {
        @Override
        public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
            for (Callback callback : callbacks) {
                if (callback instanceof NameCallback) {
                    ((NameCallback) callback).setName("admin");
                } else if (callback instanceof PasswordCallback) {
                    ((PasswordCallback) callback).setPassword("admin123".toCharArray());
                } else {
                    throw new UnsupportedCallbackException(callback);
                }
            }
        }
    }
}
 

설명:

  • LoginContext: JAAS에서 인증을 처리하는 클래스입니다.
  • login: 인증을 시도하는 메서드입니다.
  • Subject: 인증된 사용자를 나타내는 클래스입니다.
  • CallbackHandler: 인증 과정에서 사용자 입력을 처리하는 클래스입니다.
  • 위 코드에서는 JAAS를 사용하여 사용자 이름과 비밀번호로 인증을 수행합니다.

추가 예제: JAAS 정책 파일 사용

jaas.config 파일:

SampleLoginModule {
    com.sun.security.auth.module.JndiLoginModule required;
};
 

예제: JAAS 정책 파일 설정

import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

public class JAASPolicyExample {
    public static void main(String[] args) {
        // 정책 파일 설정
        System.setProperty("java.security.auth.login.config", "jaas.config");

        try {
            // LoginContext 생성 및 인증 시도
            LoginContext lc = new LoginContext("SampleLoginModule");
            lc.login();
            System.out.println("Authentication successful");
        } catch (LoginException e) {
            System.out.println("Authentication failed: " + e.getMessage());
        }
    }
}
 

설명:

  • java.security.auth.login.config: JAAS 정책 파일 경로를 설정하는 시스템 속성입니다.
  • LoginContext: JAAS에서 인증을 처리하는 클래스입니다.
  • 위 코드에서는 정책 파일을 사용하여 JAAS 인증을 수행합니다.

이로써 Java의 보안과 암호화에 대해 자세히 설명하고, 각 개념을 코드와 예시를 통해 설명했습니다. 이를 통해 보안의 기본 개념, 암호화 기법, 그리고 인증과 권한 관리 등을 이해하고 활용할 수 있게 됩니다. 다음 챕터에서는 Spring Framework에 대해 다루겠습니다.

 
 
반응형

'IT 강좌(IT Lectures) > Java' 카테고리의 다른 글

14강. 데이터베이스 연동  (0) 2024.07.01
13강. Spring 프레임워크  (0) 2024.06.30
11강. Java 성능 최적화  (0) 2024.06.28
10강. 기초 프로젝트  (0) 2024.06.27
9강. 파일 I/O 및 네트워킹  (0) 2024.06.26