• 티스토리 홈
  • 프로필사진
    알쓸개잡
  • 방명록
  • 공지사항
  • 태그
  • 블로그 관리
  • 글 작성
알쓸개잡
  • 프로필사진
    알쓸개잡
    • 분류 전체보기 (93)
      • 스프링부트 (52)
      • AWS (5)
      • 쿠버네티스 (7)
      • 자바 (19)
      • 인프라 (1)
      • ETC (8)
  • 방문자 수
    • 전체:
    • 오늘:
    • 어제:
  • 최근 댓글
      등록된 댓글이 없습니다.
    • 최근 공지
        등록된 공지가 없습니다.
      • 반응형
      # Home
      # 공지사항
      #
      # 태그
      # 검색결과
      # 방명록
      • java library를 이용한 DKIM 서명 및 검증 기능 구현하기
        2023년 11월 12일
        • 알쓸개잡
        • 작성자
        • 2023.11.12.:38
        반응형

        java에서는 Email의 DKIM 서명 및 검증 처리를 위한 몇 가지 라이브러리를 제공한다. 

        이번 포스팅에서는 Email DKIM 서명 및 검증 구현을 위한 다음 라이브러리를 사용하는 방법에 대해서 기록한다.

        • apache james 프로젝트의 jdkim 라이브러리
        • SimpleJavaMail dkim 라이브러리

        Email DKIM에 대한 상세 스펙에 대한 내용은 아래 포스팅을 참고하기 바란다.

        2023.11.08 - [ETC] - Email DKIM(DomainKeys Identified Mail) 메시지 서명 및 검증 프로세스

         

        Email DKIM(DomainKeys Identified Mail) 메시지 서명 및 검증 프로세스

        DomainKeys Identified Mail (이하 DKIM)은 공개키 암호화 및 키 서버 기술을 사용하는 이메일에 대한 도메인 수준 인증 프레임워크를 정의하여 MTA(메일 전송 에이전트) 또는 MUA(메일 사용자 에이전트)에

        devel-repository.tistory.com

         

        우선 Email DKIM 기능을 제공하기 위해서는 RSA 개인키/공개키가 필요하며 공개키는 DNS의 TXT 레코드에 등록이 되어야 한다.

        테스트를 위해서 실제 DNS에 공개키를 등록하지는 않을 것이다. 공개키를 코드 내에서 가져오도록 하여 검증 테스트는 진행할 것이다.

         

        apache james 의 jdkim 라이브러리는 서명 및 검증을 위한 코드를 제공한다.

        SimpleJavaMail dkim 라이브러리는 서명을 위한 코드를 제공한다. 또한 DKIM-Signature 헤더를 붙여 메일을 전송할 수도 있다.

         

        public / private key 생성

        DKIM 기능 제공을 위한 환경 구성을 위해서는 기본적으로 공개키(검증)/개인키(서명)가 필요하다.

        $> openssl genrsa -out rsa.private 1024

        위 명령은 1024 비트의 PEM 형식의 PKCS1 개인키를 생성한다.

         

        공개키는 다음과 같이 생성한다.

        $> openssl rsa -in rsa.private -out rsa.public -pubout -outform PEM

         

        java security 패키지는 PKCS1을 지원하지 않기 때문에 java security를 사용하여 개인키를 로딩하기 위해서는 PKCS8로 변환이 필요하다. PKCS1 -> PKCS8 변환 명령은 다음과 같다.

        $> openssl pkey -in rsa.private -out pkcs8.rsa.private

        위 명령은 PKCS8 개인키를 pkcs8.rsa.private 파일에 저장한다.

        RSA 키 파일 경로
        key 파일 저장 경로

        DKIM 서명 및 검증을 위한 샘플 코드 프로젝트의 key 파일 경로는 위와 같다.

        pkcs8.rsa.private 파일은 PKCS8 개인키 파일이다.

        rsa.private 파일은 PKCS1 개인키 파일이다.

        PKCS1, PKCS8 둘 중 하나를 선택해서 사용하면 되겠다.

        rsa.public 파일은 공개키 파일이다.

         

        만약 PKCS1 개인키를 로딩하려면 BouncyCastle 라이브러리를 사용한다.

        디펜던시는 다음과 같다.

        <!-- PKCS1 private key를 로딩하기 위한 라이브러리 -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.69</version>
        </dependency>

        우선 bouncycastle 디펜던시 추가가 필요하다. PKCS1 개인키를 로딩하는 코드는 다음과 같다.

        //PKCS1 유형의 private key를 loading 하기 위해서 BouncyCastle 라이브러리 사용
        //java는 PKCS1을 지원하지 않는다.
        //https://www.baeldung.com/java-read-pem-file-keys#2-read-private-key
        //혹은 PKCS1 -> PKCS8로 변환 후
        public static RSAPrivateKey loadPrivateKeyPKCS1(Path keyPath) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
            Security.addProvider(new BouncyCastleProvider());
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            try (FileReader keyReader = new FileReader(keyPath.toFile());
                 PemReader pemReader = new PemReader(keyReader)) {
                PemObject pemObject = pemReader.readPemObject();
                byte[] content = pemObject.getContent();
                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(content);
                return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
            }
        }

         

        이번 포스팅에서 구현하는 샘플은 PKCS8 개인키를 사용한다.

         

         

        샘플 Email

        테스트를 위한 샘플 EML 파일 위치는 다음과 같다.

        src/main/resources/sample.eml

         

        라이브러리 dependency

        apache james jdkim

        <!-- apache james jdkim 라이브러리 -->
        <dependency>
            <groupId>org.apache.james.jdkim</groupId>
            <artifactId>apache-jdkim</artifactId>
            <version>0.3</version>
        </dependency>

         

        simplejavamail dkim module

        <!-- simplejavamail의 dkim 라이브러리 -->
        <dependency>
            <groupId>org.simplejavamail</groupId>
            <artifactId>utils-mail-dkim</artifactId>
            <version>3.0.0</version>
        </dependency>

         

         

        Email 메시지 서명하기 (DKIM-Signature 헤더 생성하기)

        샘플 코드에서 메시지 서명에 사용되는 필수 태그는 다음과 같다.

        • a=rsa-sha256
        • s=selector
        • c=simple/simple
        • d=test.com
        • h=Content-Type:MIME-Version:Subject:Message-ID:To:Sender:From:Date

        Key Loader 클래스

        개인키 파일 로딩을 위해 사용되는 공통 클래스다.

        package com.example.spring.dkim.common;
        
        import org.apache.james.jdkim.codec.binary.Base64;
        import org.bouncycastle.jce.provider.BouncyCastleProvider;
        import org.bouncycastle.util.io.pem.PemObject;
        import org.bouncycastle.util.io.pem.PemReader;
        
        import java.io.FileReader;
        import java.io.IOException;
        import java.nio.charset.Charset;
        import java.nio.file.Files;
        import java.nio.file.Path;
        import java.security.KeyFactory;
        import java.security.NoSuchAlgorithmException;
        import java.security.Security;
        import java.security.interfaces.RSAPrivateKey;
        import java.security.spec.InvalidKeySpecException;
        import java.security.spec.PKCS8EncodedKeySpec;
        
        public class RSAKeyLoader {
        	//PKCS1 private key를 loading 하기 위해서 BouncyCastle 라이브러리 사용
        	//java는 PKCS1을 지원하지 않는다.
        	//https://www.baeldung.com/java-read-pem-file-keys#2-read-private-key
        	public static RSAPrivateKey loadPrivateKeyPKCS1(Path keyPath) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
        		Security.addProvider(new BouncyCastleProvider());
        		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        		try (FileReader keyReader = new FileReader(keyPath.toFile());
        			 PemReader pemReader = new PemReader(keyReader)) {
        			PemObject pemObject = pemReader.readPemObject();
        			byte[] content = pemObject.getContent();
        			PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(content);
        			return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        		}
        	}
        
        	//PKCS8 private key를 loading
        	public static RSAPrivateKey loadPrivateKeyPKCS8(Path keyPath) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
        		String privateKeyData = Files.readString(keyPath, Charset.defaultCharset());
        		String privateKeyPEM = privateKeyData
        			.replace("-----BEGIN PRIVATE KEY-----", "")
        			.replace("-----END PRIVATE KEY-----", "");
        		byte[] encoded = Base64.decodeBase64(privateKeyPEM);
        		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
        		return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        	}
        }

         

        apache james 라이브러리

        다음은 apache james 라이브러리를 이용하여 DKIM-Signature를 생성하는 클래스다.

        package com.example.spring.dkim.sign;
        
        import com.example.spring.dkim.common.RSAKeyLoader;
        import jakarta.mail.MessagingException;
        import lombok.extern.slf4j.Slf4j;
        import org.apache.james.jdkim.DKIMSigner;
        import org.apache.james.jdkim.exceptions.FailException;
        import org.springframework.core.io.ClassPathResource;
        
        import java.io.IOException;
        import java.io.InputStream;
        import java.io.OutputStream;
        import java.nio.charset.Charset;
        import java.nio.file.Files;
        import java.nio.file.Path;
        import java.security.NoSuchAlgorithmException;
        import java.security.PrivateKey;
        import java.security.spec.InvalidKeySpecException;
        
        @Slf4j
        public class JamesDKIMSign {
        	public JamesDKIMSign() {}
        	public void makeDkimSignMessage(Path originEml, Path outputEml) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException, FailException, MessagingException {
        		//DKIM-Signature header tag 셋팅
        		String signatureTemplate =
        			"v=1; a=rsa-sha256; s=selector; c=simple/simple; d=test.com; " +
        			"h=Content-Type:MIME-Version:Subject:Message-ID:To:Sender:From:Date; bh=; b=";
        
        		//PKCS8 개인키를 로딩한다.
        		ClassPathResource privateKeyResource = new ClassPathResource("keys/pkcs8.rsa.private");
        		PrivateKey privateKey = RSAKeyLoader.loadPrivateKeyPKCS8(privateKeyResource.getFile().toPath());
        		//PKCS1 개인키를 로딩하려면 아래 메서드를 호출한다.
        //		ClassPathResource privateKeyResource = new ClassPathResource("keys/rsa.private");
        //		PrivateKey privateKey = RSAKeyLoader.loadPrivateKeyPKCS1(privateKeyResource.getFile().toPath());
        
        		DKIMSigner signer = new DKIMSigner(signatureTemplate, privateKey);
        		String sign;
        		try (InputStream inputStream = Files.newInputStream(originEml)) {
        			//sign() 메서드 호출을 통해서 DKIM-Signature 헤더를 얻을 수 있다.
        			//sign() 호출 이후 inputStream은 close 된다.
        			sign = signer.sign(inputStream);
        			log.info("{}", sign);
        			sign += "\r\n";
        		}
        
        		//outputEml 에 DKIM-Signature 헤더를 추가하여 originEml을 복사한다.
        		try (InputStream inputStream = Files.newInputStream(originEml);
        			OutputStream outputStream = Files.newOutputStream(outputEml)) {
        			outputStream.write(sign.getBytes(Charset.defaultCharset()));
        			outputStream.write(inputStream.readAllBytes());
        		}
        	}
        }
        //target/classes/sample.eml 파일에 대한 DKIM-Signature 헤더를 생성하여
        //DKIM-Signature 헤더가 삽입된 target/classes/sample-james-dkim.eml 파일을 생성한다.
        Path jamesSign() {
            try {
                JamesDKIMSign jamesDKIMSign = new JamesDKIMSign();
                ClassPathResource resource = new ClassPathResource("sample.eml");
                Path originEmlPath = resource.getFile().toPath();
                Path outputEmlPath = originEmlPath.resolveSibling("sample-james-dkim.eml");
                jamesDKIMSign.makeDkimSignMessage(originEmlPath, outputEmlPath);
                return outputEmlPath;
            } catch (Exception e) {
                log.error("fail to dkim sign by james library, {}", e.getMessage());
                return null;
            }
        }
        • apache james 라이브러리의 경우 메시지 서명을 위해서는 우선 signatureTemplate를 미리 정의해야 한다.
        • signatureTemplate에서 b=, bh= 태그는 모두 공백문자로 세팅해야 한다.
        • PrivateKey 인스턴스를 전달해야 한다.
        • PrivateKey 인스턴스는 src/main/resources/keys/pkcs8.rsa.private 파일로부터 생성된다.

         

        위 코드에서 DKIMSigner.sign() 메서드를 호출하여 얻은 DKIM-Signature 헤더는 다음과 같다.

        DKIM-Signature: a=rsa-sha256; b=W0kFoDrivKbcY3xWfMWg2Z2YogyFy7e/rilBh5CBMDZwbbi/jNKbnC/yvlgyceYexk0jVRIoB0N2BYVB3j9P4K07XOS79TKdEVvyjmckN8UdnVS7338EezB8t6v/O3bNVRzSt5YsJF1RZXTUE2LJfV/eBaIqlOoo7DetgtCXZqQ=; s=selector; c=simple/simple; d=test.com; v=1; bh=QUg010Sn3BHY0WzkYjKB6QDkD5XZxhSEsiPcC0PyX0E=; h=Content-Type:MIME-Version:Subject:Message-ID:To:Sender:From:Date;

        하지만 다음과 같은 문제가 있다.

        • james jdkim 라이브러리를 통해서 얻은 DKIM-Signature 헤더는 folding이 되지 않는다.
        • Email 메시지 헤더의 한 라인 길이의 제한이 있기 때문에 이를 그대로 사용하기에는 무리가 있다.

         

        SimpleJavaMail 라이브러리

        다음은 SimpleJavaMail 라이브러리를 이용하여 DKIM-Signature를 생성하는 클래스다.

        package com.example.spring.dkim.sign;
        
        import com.example.spring.dkim.common.RSAKeyLoader;
        import jakarta.mail.MessagingException;
        import jakarta.mail.internet.MimeMessage;
        import org.simplejavamail.utils.mail.dkim.Canonicalization;
        import org.simplejavamail.utils.mail.dkim.DkimMessage;
        import org.simplejavamail.utils.mail.dkim.DkimSigner;
        import org.simplejavamail.utils.mail.dkim.SigningAlgorithm;
        import org.springframework.core.io.ClassPathResource;
        
        import java.io.IOException;
        import java.io.InputStream;
        import java.io.OutputStream;
        import java.nio.file.Files;
        import java.nio.file.Path;
        import java.security.NoSuchAlgorithmException;
        import java.security.interfaces.RSAPrivateKey;
        import java.security.spec.InvalidKeySpecException;
        
        public class SimpleJavaDKIMSign {
        
        	/**
        	 * @param originEml DKIM 서명하고자 하는 EML 경로
        	 * @param outputEml DKIM 서명 헤더를 붙인 새로운 EML 경로
        	 */
        	public void makeDkimSignMessage(Path originEml, Path outputEml) throws IOException, MessagingException, InvalidKeySpecException, NoSuchAlgorithmException {
        		try (InputStream inputStream = Files.newInputStream(originEml);
        			 OutputStream outputStream = Files.newOutputStream(outputEml)) {
        			MimeMessage originMessage = new MimeMessage(null, inputStream);
        			DkimMessage dkimMessage = getDkimMessage(originMessage);
        			dkimMessage.writeTo(outputStream);
        		}
        
        		//check exists DKIM-Signature header
        		try (InputStream inputStream = Files.newInputStream(outputEml)) {
        			MimeMessage outputMessage = new MimeMessage(null, inputStream);
        			String[] header = outputMessage.getHeader("DKIM-Signature");
        			assert header.length > 0;
        		}
        	}
        
        	private DkimMessage getDkimMessage(MimeMessage message) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException, MessagingException {
        		final String domain = "test.com";
        		final String selector = "selector";
        		ClassPathResource privateKeyResource = new ClassPathResource("keys/pkcs8.rsa.private");
        		RSAPrivateKey privateKey = RSAKeyLoader.loadPrivateKeyPKCS8(privateKeyResource.getFile().toPath());
        
        		DkimSigner dkimSigner = new DkimSigner(domain, selector, privateKey);
        		dkimSigner.setHeaderCanonicalization(Canonicalization.SIMPLE);
        		dkimSigner.setBodyCanonicalization(Canonicalization.SIMPLE);
        		dkimSigner.setSigningAlgorithm(SigningAlgorithm.SHA256_WITH_RSA);
        
        		//지정된 도메인 및 지정된 selector에 대한 DNS 리소스 레코드가 올바르게 준비되었는지 확인하는 메서드다.
        		//테스트를 위해서 실제로 DNS 구성을 하지 않았기 때문에 false로 지정한다.
        		//리얼 환경에서는 아래 코드는 지정하지 않는 것이 좋다.
        		dkimSigner.setCheckDomainKey(false);
        		return new DkimMessage(message, dkimSigner);
        	}
        }

         

        //target/classes/sample.eml 파일에 대한 DKIM-Signature 헤더를 생성하여
        //DKIM-Signature 헤더가 삽입된 target/classes/sample-simple-javamail-dkim.eml 파일을 생성한다.
        Path simpleJavaMailSign() {
            try {
                SimpleJavaDKIMSign simpleJavaDKIMSign = new SimpleJavaDKIMSign();
                ClassPathResource resource = new ClassPathResource("sample.eml");
                Path originEmlPath = resource.getFile().toPath();
                Path outputEmlPath = originEmlPath.resolveSibling("sample-simple-javamail-dkim.eml");
                simpleJavaDKIMSign.makeDkimSignMessage(originEmlPath, outputEmlPath);
                return outputEmlPath;
            } catch (Exception e) {
                log.error("fail to dkim sign by simple java mail library, {}", e.getMessage());
                return null;
            }
        }

         

        SimpleJavaMail 라이브러리를 사용하여 DKIM-Signature 헤더를 생성하는 이점은 다음과 같다.

        • DKIM-Signature 헤더에 대한 folding이 자동 처리된다.
        • 원문 EML에 대한 DKIM-Signature 헤더가 삽입된 새로운 EML을 생성하는 메서드를 제공한다. (DkimMessage.writeTo())
        • signatureTemplate를 지정할 필요 없이 메서드를 통해 태그를 지정할 수 있다.
        • 샘플코드에서는 비활성화 하였지만 공개키 조회를 위한 DNS 구성을 확인할 수 있는 메서드를 제공한다. (setCheckDomainKey(true))

        생성된 DKIM-Signature 헤더는 다음과 같다.

        DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=simple/simple; t=1589272539;
        	s=selector; d=test.com;
        	h=Content-Type:MIME-Version:Subject:Message-ID:To:Sender:From:Date;
        	bh=QUg010Sn3BHY0WzkYjKB6QDkD5XZxhSEsiPcC0PyX0E=;
        	b=ZssTrtszInGe9heQjn5dSirqtjOOgcB80AgGpxa6Xzu8+7hyB+3St5Hw4Lfp7rtT
        	kjXwalwuT+qK0Bi0Tvb6l5t6Y2NFtP8MDsFCjqHdDxdkz0N7b8NRA9hYBWZlwMSRk+J
        	BNoOZNAiTv/yURjAucDxmgeyc6tz2+zNFGmdzhWo=

        DKIM-Signature 헤더의 folding 처리가 잘 된 것을 확인할 수 있다.

         

        DKIM 서명 검증하기 (DKIM-Signature 헤더 검증)

        두개의 라이브러리 중에서 DKIM-Signature 헤더 검증 기능 제공은 apache james jdkim 라이브러리에서만 제공한다.

        DKIMVerifier.verify() 메서드를 통해서 검증 기능을 제공하며 PublicKeyRecordRetriever 구현체를 생성자에 전달하지 않으면 기본적으로 DNSPublicKeyRecordRetriever 인스턴스가 사용되는데 DNSPublicKeyRecordRetriever 인스턴스는 DKIM-Signature 헤더의 s=(selector), d=(domain)을 기반으로 DNS에 등록된 공개키를 얻어와서 검증을 수행한다.

        PublicKeyRecordRetriever 구현체 인스턴스를 생성자에 전달하면 공개키는 getRecords() 메서드를 통해서 얻어오게 된다.

        PublicKeyRecordRetriever 인터페이스는 다음과 같다.

        public interface PublicKeyRecordRetriever {
        
            /**
             * @param methodAndOption
             *                the options declared for the lookup method.
             * @param selector
             *                the value of "s=" tag
             * @param token
             *                the value of the "d=" tag
             * @return A list of strings representing 0 to multiple records
             * @throws TempFailException
             *                 in case of timeout and other network errors.
             * @throws PermFailException
             *                 in case of unsupported options
             */
            public List<String> getRecords(CharSequence methodAndOption,
                    CharSequence selector, CharSequence token)
                    throws TempFailException, PermFailException;
        
        }

         

        메시지 서명 및 검증을 위한 테스트로써 실제 DNS에 공개키를 등록하지 않았기 때문에 PublicKeyRecordRetriever를 구현한 구현체를 직접 정의하여 getRecords() 메서드에 공개키를 하드코딩하여 리턴하도록 할 것이다.

        다음 클래스는 공개키를 DNS 조회없이 리턴하도록 하는 클래스다.

        package com.example.spring.dkim.verify;
        
        import org.apache.james.jdkim.api.PublicKeyRecordRetriever;
        import org.apache.james.jdkim.exceptions.PermFailException;
        import org.apache.james.jdkim.exceptions.TempFailException;
        
        import java.util.List;
        
        /**
         * PublicKeyRecordRetriever 클래스는 apache james의 jdkim 라이브러리에서 제공하는 인터페이스다.
         * 서명 검증시 내부적으로 DKIM-Signature 헤더의 selector, domain 정보를 기반으로 DNS 조회를 통해서 공개키를 가져오는데
         * DNS 조회를 통해서 공개키를 가져오는 역할은 PublicKeyRecordRetriever 구현체가 담당한다.
         * 검증 테스트를 위해서 DNS에 공개키를 등록하지 않았기 때문에 하드코딩된 공개키를 가져오도록 한다.
         * PublicKeyRecordRetriever4Test 클래스는 하드코딩된 공개키 정보를 가져오도록 하기 위한 장치다.
         * p= 태그에 BEGIN PUBLIC KEY, END PUBLIC KEY 라인을 제외한
         * resource/keys/rsa.public 파일의 공개키 내용을 직접 셋팅하도록 한다.
         */
        public class PublicKeyRecordRetriever4Test implements PublicKeyRecordRetriever {
        	@Override
        	public List<String> getRecords(
        		CharSequence methodAndOption,
        		CharSequence selector,
        		CharSequence token) {
        		//example
        		//String publicKeyRecord = "v=DKIM1; k=rsa; " +
        		//	"p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjm1g0xmCM8eCveYP70SAdRNa1" +
        		//	"2y0absnxuOcZy/hvSyQqNqS5hoFcfvn/tQCO4LrcccLoRSueMOOc6l0rHpRcHluX++t" +
        		//	"FMbG7IkxcMT1Rk/vnBQ31GeTfeEbR4y/hBMdU5DDvRsvkSXXVlc9Us7m8te6hPFQYxtABCDEFGHIJKLMN";
        		String publicKeyRecord = "v=DKIM1; k=rsa; " +
        			"p=<공개키데이터>";
        		return List.of(publicKeyRecord);
        	}
        }
        PublicKeyRecordRetriever4 Test는 공개키가 DNS에 등록되어 있지 않기 때문에 테스트를 위해서 정의한 클래스 이므로 리얼 환경에서는 정의해서 사용하면 안 된다.
        리얼 환경에서는 DKIMVerifier 인스턴스 생성 시 디폴트 생성자를 통해서 인스턴스를 생성하도록 한다.

         

        다음은 DKIM-Signature 검증을 위한 클래스다.

        package com.example.spring.dkim.verify;
        
        import lombok.extern.slf4j.Slf4j;
        import org.apache.james.jdkim.DKIMVerifier;
        import org.apache.james.jdkim.api.PublicKeyRecordRetriever;
        import org.apache.james.jdkim.api.SignatureRecord;
        import org.apache.james.jdkim.exceptions.FailException;
        
        import java.io.IOException;
        import java.io.InputStream;
        import java.nio.file.Files;
        import java.nio.file.Path;
        import java.util.List;
        
        @Slf4j
        public class JamesDKIMVerify {
        	public JamesDKIMVerify() {}
        
        	public void verify(Path emlPath) throws IOException, FailException {
        		PublicKeyRecordRetriever publicKeyRecordRetriever = new PublicKeyRecordRetriever4Test();
        		try(InputStream inputStream = Files.newInputStream(emlPath)) {
        			//리얼 환경에서는 publicKeyRecordRetriever 전달없이 디폴트 생성자를 호출한다.
        			DKIMVerifier verifier = new DKIMVerifier(publicKeyRecordRetriever);
        			List<SignatureRecord> verify = verifier.verify(inputStream);
        			verify.forEach(signatureRecord -> log.info("signature record: {}", signatureRecord));
        		}
        	}
        }
        /**
         * apache james jdkim 라이브러리를 사용하여 파라미터로 전달된 경로의 EML 에 대한 DKIM 검증을 수행한다.
         * @param emlPath DKIM-Signature 헤더가 삽입된 eml 파일 경로
         */
        void verify(Path emlPath) {
            try {
                JamesDKIMVerify jamesDKIMVerify = new JamesDKIMVerify();
                jamesDKIMVerify.verify(emlPath);
            } catch (Exception e) {
                log.error("fail to verify, {}", e.getMessage());
            }
        }

         

        지금까지 apache james jdkim 라이브러리와 SimpleJavaMail 라이브러리를 사용하여 Email DKIM 서명 및 검증을 위한 샘플 코드를 작성해 보았다.

         

        결론

        apache james jdkim을 사용하여 DKIM-Signature 헤더를 생성하면 몇 가지 문제가 있다.

        • DKIM-Signature 헤더에 대해서 folding 처리가 되지 않기 때문에 헤더 라인 길이 제약에 문제가 생길 수 있다.
        • 생성된 DKIM-Signature 헤더를 삽입한 EML을 생성할 수 있는 메서드를 제공하지 않아 이를 위해서는 직접 구현해야 한다. 
        • 미리 DKIM-Signature 헤더 템플릿을 정의해야 하며 사용함에 있어서 직관성이 떨어진다는 생각이 든다.

        이에 반해 SimpleJavaMail 라이브러리를 사용하여 DKIM-Signature를 생성하면

        • DKIM-Signature 헤더에 대한 folding 처리가 자동으로 수행된다.
        • DKIM 공개키를 조회할 수 있는 DNS 구성이 제대로 되었는지 확인할 수 있는 기능을 제공한다.
        • 생성된 DKIM-Signature 헤더를 삽입하여 새로운 EML 파일을 생성할 수 있도록 메서드를 제공한다.
        • 사용에 있어서 조금 더 직관적이다.
        • DKIM-Signature 헤더를 검증할 수 있는 기능을 제공하지 않는다.

        DKIM 서명을 생성하는 코드는 SimpleJavaMail 라이브러리를 사용하고 검증을 위한 코드는 apache james jdkim 라이브러리를 사용하는 것이 좋을 듯하다.

         

        전체 샘플 코드는 gitlab에서 확인할 수 있다.

        https://gitlab.com/blog4031530/spring-dkim


        참고링크

        https://james.apache.org/jdkim/mailets/index.html

        https://github.com/simple-java-mail/java-utils-mail-dkim

        https://dkim.org/specs/rfc4871-dkimbase.html#creating-a-key

         

        반응형
        저작자표시 비영리 변경금지 (새창열림)

        'ETC' 카테고리의 다른 글

        맥 m1, m2 에 jmeter 사용하기  (0) 2023.12.02
        맥 환경에서 iTerm2 개발 환경 셋팅하기  (0) 2023.12.01
        Email DKIM(DomainKeys Identified Mail) 메시지 서명 및 검증 프로세스  (0) 2023.11.08
        Email SPF (Sender Policy Framework)  (0) 2023.10.28
        테스트 환경을 위한 mariadb, kafka docker compose  (0) 2023.09.18
        다음글
        다음 글이 없습니다.
        이전글
        이전 글이 없습니다.
        댓글
      조회된 결과가 없습니다.
      스킨 업데이트 안내
      현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
      ("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
      목차
      표시할 목차가 없습니다.
        • 안녕하세요
        • 감사해요
        • 잘있어요

        티스토리툴바