ETC

java library를 이용한 DKIM 서명 및 검증 기능 구현하기

알쓸개잡 2023. 11. 12. 18: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