배경
레거시 시스템(구형 JSch 라이브러리 등)과의 SFTP 연동을 앞두고, 대상 서버(Rocky Linux, OpenSSH 8.0)가 ssh-rsa(SHA-1 서명) 방식의 공개키 인증을 실제로 지원하는지 검증이 필요했다. 단순히 되나 안 되나를 확인하는 걸 넘어서, OS의 crypto-policy가 SSH 프로토콜 협상에 어떻게 관여하는지까지 확인하게 된 과정을 정리한다.
1. ssh-rsa(SHA-1)와 rsa-sha2-*의 차이
SSH 키 자체에 "SHA-1 키"라는 종류가 따로 있는 게 아니다. RSA 키 하나로 접속 시 서명 알고리즘을 아래 중 무엇으로 쓸지가 매 연결마다 협상된다.
ssh-rsa— RSA + SHA-1 서명 (레거시)rsa-sha2-256/rsa-sha2-512— RSA + SHA-2 서명 (권장)
OpenSSH 클라이언트는 기본적으로 가장 안전한 알고리즘을 자동 선택하므로, -i 옵션으로 RSA 키만 지정해서 접속하면 서버가 ssh-rsa를 지원하더라도 실제로는 rsa-sha2-512로 인증이 이뤄진다. 순수 SHA-1 협상을 강제하려면 명시적 옵션이 필요하다.
# 이렇게 하면 SHA-2로 자동 승격되어 SHA-1 테스트가 안 됨
ssh -i id_rsa_sha1test user@host
# 서명 알고리즘 목록을 통째로 교체(=)해야 SHA-1만 남는다 (+ 는 추가일 뿐 우선순위상 SHA-2가 먼저 시도됨)
ssh -vvv -o PubkeyAcceptedAlgorithms=ssh-rsa -i id_rsa_sha1test user@host
-vvv 로그에서 아래 줄로 실제 사용된 서명 알고리즘을 확인할 수 있다.
debug3: sign_and_send_pubkey: signing using ssh-rsa SHA256:...
2. 테스트 절차 요약
2-1. 서버 지원 여부 확인
sudo sshd -T | grep -i pubkeyacceptedalgorithms
2-2. 테스트용 RSA 키 생성 및 배포
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_sha1test
ssh-copy-id -i ~/.ssh/id_rsa_sha1test.pub user@host
ssh-copy-id 결과에서 Number of key(s) added: 1이 나오면 공개키가 authorized_keys에 정상 등록된 것이다.
2-3. SHA-1 강제 접속 테스트
ssh -vvv \
-o PubkeyAcceptedAlgorithms=ssh-rsa \
-o HostKeyAlgorithms=+ssh-rsa \
-i ~/.ssh/id_rsa_sha1test \
user@host
3. 부딪힌 문제: OpenSSL "invalid digest" 오류
옵션을 정확히 지정했음에도 아래 오류로 접속이 끊겼다.
debug1: Host 'jinhan2-splunk' is known and matches the RSA host key.
debug1: openssl error ...: digital envelope routines:do_sigver_init:invalid digest
ssh_dispatch_run_fatal: Connection to ... error in libcrypto
원인: SSH 프로토콜 레벨(ssh_config)에서는 ssh-rsa를 허용하도록 강제했지만, 더 하위 계층인 OpenSSL 라이브러리(libcrypto)가 시스템 crypto-policy(RHEL/Rocky 계열의 DEFAULT 정책)에 의해 SHA-1 다이제스트 생성 자체를 차단하고 있었다. 즉 SSH 설정과 무관하게 OS 레벨에서 SHA-1이 막혀 있던 것이다.
에러 발생 지점이 "호스트 키 서명을 SHA-1로 검증하려는 단계"였다는 점이 힌트였다 — 서버 문제가 아니라 접속을 시도하는 클라이언트 쪽 crypto-policy 문제였다.
해결
클라이언트에서 crypto-policy를 LEGACY로 일시 전환.
sudo update-crypto-policies --set LEGACY
# 반드시 새 세션(재로그인)에서 재시도 — 기존 쉘엔 정책이 캐시돼 있을 수 있음
적용 후 KEXINIT 제안에 diffie-hellman-group14-sha1, gss-gex-sha1- 등 SHA-1 계열 KEX가 포함되는 것을 확인했고, 재시도 결과:
debug1: Server host key: ssh-rsa SHA256:...
debug1: Host '...' is known and matches the RSA host key.
...
debug3: sign_and_send_pubkey: signing using ssh-rsa SHA256:...
Authenticated to ... using "publickey".
정상적으로 SHA-1(ssh-rsa) 서명으로 호스트 키 검증과 사용자 인증이 모두 완료됐다.
⚠️ LEGACY 정책은 SHA-1 외에 3DES 등 다른 취약 알고리즘도 함께 열리므로, 테스트 후에는 반드시 원복.
bash sudo update-crypto-policies --set DEFAULT
4. 트러블슈팅 요약표
| 단계 | 증상 | 원인 | 해결 |
|---|---|---|---|
| 1차 | 접속은 되나 SHA-1 테스트 아님 | PubkeyAcceptedAlgorithms 옵션 누락 → 자동으로 rsa-sha2-512 선택 |
옵션에 =ssh-rsa(완전 교체)로 지정 |
| 2차 | invalid digest / error in libcrypto |
클라이언트 OpenSSL/crypto-policy가 SHA-1 다이제스트 생성 차단 | 클라이언트 update-crypto-policies --set LEGACY + 재로그인 |
| 3차 | 정상 인증 성공 | - | - |
5. 응용: 구버전 JSch(0.1.54 이하) 라이브러리로 SFTP 연동 시 영향은?
이 테스트 결과를 구버전 JSch 기반 SFTP 연동에 적용할 수 있는지 검토했다.
핵심 차이점: OpenSSH가 겪은 invalid digest 문제는 OS의 OpenSSL(libcrypto)이 시스템 crypto-policy에 의해 SHA-1을 막은 것이었다. 반면 JSch는 순수 Java 구현체로 OS OpenSSL이 아니라 JVM 내장 JCE를 사용하므로, OS crypto-policy(FIPS/DEFAULT/LEGACY)의 영향을 받지 않는다. 대부분의 JDK는 SHA1withRSA 서명 알고리즘을 기본적으로 활성화해두고 있어, OS를 LEGACY로 바꾸지 않아도 JSch는 SHA-1 서명을 정상적으로 만들어낼 가능성이 높다.
다만 아래는 별도 확인이 필요하다.
- 서버가
PubkeyAcceptedAlgorithms에ssh-rsa를 계속 허용하고 있어야 함 (JSch 0.1.54 이하는rsa-sha2-*를 아예 모르고 무조건ssh-rsa로만 서명 시도 — 서버가 이를 막으면 접속 자체가 불가) - 서버가 구형 KEX(
diffie-hellman-group14-sha1등)를 함께 제공해야 구버전 JSch와 KEX 협상이 성립 - 최신 OpenSSH가 보내는
kex-strict-s-v00@openssh.com같은 확장 이름을 구버전 JSch가 무시하고 넘어가는지는 라이브러리 버전마다 다를 수 있어 별도 검증 필요
최소 연결 검증 코드
import com.jcraft.jsch.*;
public class SftpTest {
public static void main(String[] args) throws Exception {
JSch jsch = new JSch();
jsch.addIdentity("/home/jinhan2/.ssh/id_rsa_sha1test");
Session session = jsch.getSession("jinhan2", "jinhan2-splunk", 22);
session.setConfig("StrictHostKeyChecking", "no");
session.connect(10000);
ChannelSftp sftp = (ChannelSftp) session.openChannel("sftp");
sftp.connect();
System.out.println("SFTP 연결 성공, pwd=" + sftp.pwd());
sftp.disconnect();
session.disconnect();
}
}
6. Rocky Linux에서 Java 소스 실행하기 (JDK 설치부터)
javac: command not found — JDK 미설치가 원인이었다.
# 사용 가능한 패키지 검색 (module 목록이 비어도 정상, 패키지로 바로 검색)
dnf search openjdk
# JDK 17(LTS) 설치 — devel 패키지가 있어야 javac(컴파일러)가 포함됨
sudo dnf install -y java-17-openjdk-devel
# 설치 확인
javac -version
java -version
만약 AppStream 리포지토리가 비활성화되어 패키지를 못 찾는다면:
dnf repolist
sudo dnf config-manager --set-enabled appstream
sudo dnf makecache
컴파일 및 실행
# JSch jar 다운로드
wget https://repo1.maven.org/maven2/com/jcraft/jsch/0.1.54/jsch-0.1.54.jar
# 컴파일
javac -cp jsch-0.1.54.jar SftpTest.java
# 실행
java -cp .:jsch-0.1.54.jar SftpTest
Java 11 이상이면 컴파일 단계 없이 소스 파일을 바로 실행하는 것도 가능하다.
java -cp jsch-0.1.54.jar SftpTest.java
정리
ssh-rsa(SHA-1) 테스트는PubkeyAcceptedAlgorithms=ssh-rsa(완전 교체)로 명시해야 정확히 검증된다.- SSH 설정과 별개로 OS crypto-policy가 OpenSSL 레벨에서 SHA-1을 차단할 수 있으며, 이 경우 SSH 옵션만으로는 해결되지 않고
update-crypto-policies조정이 필요하다. - 구버전 JSch는 OS crypto-policy와 무관하게 JVM 자체 JCE로 서명하므로 OpenSSH client 테스트와는 별도로 검증이 필요하지만, 서버가
ssh-rsa와 legacy KEX를 허용하는 현재 상태라면 정상 동작할 가능성이 높다. - 레거시 알고리즘 테스트 후에는 보안을 위해 crypto-policy를 원래 상태로 되돌리는 것을 잊지 말 것.

댓글목록
등록된 댓글이 없습니다.