안녕하세요. 오픈소스컨설팅 Solution Architect 2팀 장준석입니다.

이번 포스팅에서는 Atlassian Crowd 제품에서 사용하고 있는 암호 알고리즘에 대해 소개하려 합니다.

Crowd란

Atlassian Crowd는 여러 애플리케이션을 사용하는 기업을 위해 설계된 Single Sign-On 및 ID 관리 솔루션입니다. 사용자는 하나의 자격 증명 세트로 여러 애플리케이션에 로그인할 수 있으므로 인증 프로세스가 간소화되고 보안이 강화됩니다. Crowd는 Jira, Confluence, Bitbucket과 같은 Atlassian 자체 소프트웨어 제품과 타사 애플리케이션을 비롯한 여러 애플리케이션에서 중앙 집중식 사용자 관리, 사용자 디렉터리 동기화 및 권한 관리를 제공합니다. 또한 맞춤형 애플리케이션과의 통합을 위한 REST API를 제공합니다.

전반적으로 Atlassian Crowd는 조직이 애플리케이션 및 데이터에 대한 액세스를 보다 효율적이고 안전하게 관리할 수 있도록 도와줍니다.

Crowd가 암호화하는 데이터

Crowd에서 암호화하는 데이터의 종류는 다음과 같습니다.

  • LDAP 디렉토리 비밀번호
  • 원격 Crowd 디렉토리 어플리케이션 비밀번호
  • Azure AD 웹 어플리케이션 비밀번호
  • SMTP 메일 비밀번호
  • Proxy 비밀번호

Crowd가 사용하는 암호 알고리즘

Crowd에서는 AES/CBC/PKCS5Padding 알고리즘을 사용해 패스워드를 암호화 합니다.

이 중 AES 암호화가 메인이며, AES 암호화에 필요한 요소로 CBC와 PKCS5Padding이 사용됩니다.

  • AES (Advanced Encryption Standard)
  • CBC (Cipher Block Chaining)
  • PKCS5Padding

다음은 각 알고리즘에 대해 알아보겠습니다.

AES (Advanced Encryption Standard)

AES(Advanced Encryption Standard)는 고급 암호 표준을 의미하며, 가장 안전한 암호화 표준 중 하나로 널리 사용되는 암호화 알고리즘입니다.

AES는 대칭 암호 방식입니다. 암호화와 암호 해독에 동일한 키가 사용되며 고정 길이의 데이터 블록에서 작동합니다. 대칭 암호 방식의 특징은 비트 단위로 암호화를 하기 때문에 비대칭 암호화 방식보다 상대적으로 빠른 속도를 제공하고 있습니다. AES 128, 192 또는 256 비트의 키 길이를 사용하며 키 길이가 길수록 보안이 강화됩니다. 알고리즘 자체는 SubBytes, ShiftRows, MixColumns 및 AddRoundKey의 네 가지 작업의 조합으로 구성됩니다.

  • SubBytes : 이 단계에서는 입력 데이터의 각 바이트는 S-box라고 하는 고정 대체 테이블의 해당 바이트로 대체됩니다. 이는 입력 데이터와 암호문 사이의 관계를 식별하기 더 어렵게 만들어 줍니다.
  • ShiftRows : 이 단계에서는 128비트 블록의 행이 행 번호에 따라 특정 바이트 수만큼 왼쪽으로 이동합니다. 이는 데이터를 분산시키고 암호문에서 패턴을 식별하기 어렵게 만들어 줍니다.
  • MixColumns : 이 단계에서는 128비트 블록의 각 열에 Galois 필드 산술을 사용하여 고정 행렬을 곱해줍니다.
  • AddRoundKey : 이 단계에서는 128비트 블록의 각 바이트는 비트별 XOR 연산을 사용하여 비밀 키의 해당 바이트와 결합됩니다. 비밀 키를 모르면 해독하기가 더 어려워집니다.

위의 네 가지 작업은 여러 라운드 동안 반복되며, 라운드의 수는 키 길이에 따라 다릅니다. 키가 길수록 더 많은 라운드를 반복하게 됩니다.

요약하면 AES 알고리즘은 위의 네 가지 작업을 통해 일반 텍스트를 암호문으로 변환합니다. AES의 강점은 비밀 키를 알지 못하는 상태에서는 암호화를 해독하는 것을 극도로 어렵게 만든다는 것입니다.

CBC (Cipher Block Chaining)

블록 암호 운영 방식은 평문을 일정한 크기의 블록으로 잘라낸 후 각 블록을 암호화하는 방식입니다. 블록 암호 운영 방식에는 ECB, CBC, OFB, CFB,…등 많은 종류가 있습니다.

Crowd에서 사용되는 CBC의 경우에는 암호화 키 에다가 IV(초기화 벡터) 라는 값을 추가하여 A를 암호화 하면 항상 B가 나오던 문제를 해결하여 역추적을 불가능하게 만들었습니다. 하지만 중간자 공격 취약점이 존재합니다. 중간자 공격 취약점이란, A를 암호화 하여 B가 나왔는데, 이게 진짜 A를 함호화 해서 나온 값이 맞는지에 대해 검증하는 부분이 없는 것 입니다. 따라서 HTTP 환경이나, 메모리 해킹 등을 통하여 중간에서 가로챈 뒤 가짜 데이터를 전달 받을 경우 검증할 수 없습니다. ECB 보다는 훨씬 진보적인 방법이지만 취약점이 존재합니다.

PKCS5Padding

암호화에서 Padding은 암호화 하려는 데이터의 마지막 블록 크기가 블록 크기보다 작다면, 마지막 블록에 패딩 값을 추가하여 블록 크기를 맞춰줍니다. 암호화 전에 메시지의 시작, 중간 또는 끝에 데이터를 추가하여 블록 크기를 맞추는 것입니다. 패딩 모드는 2가지로 나뉘며 PKCS5Padding 와 PKCS7Padding로 나뉩니다. 둘은 동작 방식은 같으나 PKCS5는 블록 사이즈가 8바이트에 대해서만 정의되어 있고, PKCS7은 16바이트까지 동작합니다.

PKCS5padding은 RFC2898에 정의된 padding 방식으로 암호화하려는 데이터가 블록의 배수가 아닐 경우 다음과 같이 부족한 길이만큼 padding을 하는 방식입니다.

01
02 02 
03 03 03
04 04 04 04
05 05 05 05 05
06 06 06 06 06 06
07 07 07 07 07 07 07

예시를 통해 padding 값 계산을 해보겠습니다.

예시 1) 원본 데이터가 OB라는 값으로 끝나고 8바이트가 블록 사이즈입니다. 암호화 하려는 원본이 18바이트라면 2개의 블록과 2바이트가 암호화 대상이 됩니다.

→ 블록 크기를 맞추려면 6바이트가 부족하므로 06을 6번 padding합니다.

AB OB 06 06 06 06 06 06 

예시 2) 원본 데이터가 AA라는 값으로 끝나고 8바이트 블록 사이즈입니다. 암호화 하려는 원본이 9바이트라면 1개의 블로과 1바이트가 암호화 대상이 됩니다.

→ 블록 크기를 맞추려면 7바이트가 부족하므로 07을 7번 Padding합니다.

AA 07 07 07 07 07 07 07

Crowd에서 암호화 적용 확인하기

이번에는 Crowd에 AES/CBC/PKCS5Padding 암호화가 실제로 적용되어 있는지 확인해 보겠습니다.

Crowd에서는 DbConfigPasswordCipherEncryptorsFactory.class 클래스를 이용하여 암호화를 합니다.해당 클래스의 경로는 다음과 같습니다.

/<crowd-engine-dir>/crowd-webapp/WEB-INF/lib/crowd-db-config-password-cipher-encryptors-5.1.0.jar
com/atlassian/crowd/crypto/DbConfigPasswordCipherEncryptorsFactory.class
/*
 * Decompiled with CFR 0.152.
 *
 * Could not load the following classes:
 *  com.atlassian.crowd.common.properties.BooleanSystemProperty
 *  com.atlassian.crowd.common.properties.DurationSystemProperty
 *  com.atlassian.crowd.common.properties.EncryptionProperties
 *  com.atlassian.crowd.common.properties.IntegerSystemProperty
 *  com.atlassian.crowd.crypto.Algorithm
 *  com.atlassian.crowd.crypto.CachedEncryptor
 *  com.atlassian.crowd.crypto.MissingKeyHandlingEncryptor
 *  com.atlassian.crowd.crypto.SaltingEncryptor
 *  com.atlassian.crowd.embedded.api.Encryptor
 *  com.atlassian.crowd.manager.property.EncryptionSettings
 *  com.atlassian.db.config.password.CipherProvider
 *  com.atlassian.db.config.password.DefaultCipherProvider
 *  java.lang.Integer
 *  java.lang.Object
 *  java.lang.String
 *  java.lang.invoke.LambdaMetafactory
 *  java.time.Duration
 *  java.util.Collections
 *  java.util.HashMap
 *  java.util.Map
 *  java.util.function.BooleanSupplier
 */
package com.atlassian.crowd.crypto;

import com.atlassian.crowd.common.properties.BooleanSystemProperty;
import com.atlassian.crowd.common.properties.DurationSystemProperty;
import com.atlassian.crowd.common.properties.EncryptionProperties;
import com.atlassian.crowd.common.properties.IntegerSystemProperty;
import com.atlassian.crowd.crypto.Algorithm;
import com.atlassian.crowd.crypto.CachedEncryptor;
import com.atlassian.crowd.crypto.DbConfigPasswordCipherEncryptor;
import com.atlassian.crowd.crypto.MissingKeyHandlingEncryptor;
import com.atlassian.crowd.crypto.SaltingEncryptor;
import com.atlassian.crowd.embedded.api.Encryptor;
import com.atlassian.crowd.manager.property.EncryptionSettings;
import com.atlassian.db.config.password.CipherProvider;
import com.atlassian.db.config.password.DefaultCipherProvider;
import java.lang.invoke.LambdaMetafactory;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BooleanSupplier;

public class DbConfigPasswordCipherEncryptorsFactory {
    private DbConfigPasswordCipherEncryptorsFactory() {
    }

    public static Map<String, Encryptor> createEncryptorsMap(EncryptionSettings encryptionSettings) {
        DefaultCipherProvider cipherProvider = new DefaultCipherProvider();
        HashMap encryptors = new HashMap();
        encryptors.put((Object)Algorithm.AES.getKey(), (Object)DbConfigPasswordCipherEncryptorsFactory.createDbConfigPasswordCipherEncryptor("AES/CBC/PKCS5Padding", "AES", encryptionSettings, cipherProvider));
        encryptors.put((Object)Algorithm.DES.getKey(), (Object)DbConfigPasswordCipherEncryptorsFactory.createDbConfigPasswordCipherEncryptor("DES/CBC/PKCS5Padding", "DES", encryptionSettings, cipherProvider));
        encryptors.put((Object)Algorithm.DESEDE.getKey(), (Object)DbConfigPasswordCipherEncryptorsFactory.createDbConfigPasswordCipherEncryptor("DESede/CBC/PKCS5Padding", "DESede", encryptionSettings, cipherProvider));
        return Collections.unmodifiableMap((Map)encryptors);
    }

    private static Encryptor createDbConfigPasswordCipherEncryptor(String algorithm, String algorithmKey, EncryptionSettings encryptionSettings, DefaultCipherProvider cipherProvider) {
        DbConfigPasswordCipherEncryptor encryptor = new DbConfigPasswordCipherEncryptor(algorithm, algorithmKey, encryptionSettings, (CipherProvider)cipherProvider);
        return new MissingKeyHandlingEncryptor((Encryptor)new SaltingEncryptor((Encryptor)new CachedEncryptor((Encryptor)encryptor, (long)((Integer)EncryptionProperties.ENCRYPTION_CACHE_MAX_SIZE.getValue()).intValue(), (Duration)EncryptionProperties.ENCRYPTION_CACHE_EXPIRATION.getValue(), (BooleanSupplier)LambdaMetafactory.metafactory(null, null, null, ()Z, getValue(), ()Z)((BooleanSystemProperty)EncryptionProperties.ENRCYPTION_CACHE_ENABLED))));
    }
}

DbConfigPasswordCipherEncryptorsFactory.class를 확인해 보니 Crowd에 AES/CBC/PKCS5Padding 암호화가 적용되어 있는 것을 확인할 수 있습니다.

마치며

이 글을 통해 crowd에서 사용되는 암호 알고리즘을 이해하는데 조금이나 도움이 되셨으면 좋겠습니다.긴 글 읽어주셔 감사합니다. 다음에는 좀 더 흥미로운 주제로 찾아오겠습니다.

감사합니다.

Leave a Reply

Your email address will not be published. Required fields are marked *