[오픈소스] 특수문자를 10개만 아는 강도 검사기를 고치다🔐 [Password-Strength-Tester-and-Cracker]

2026. 7. 27. 22:04·◈ Yermi Project/오픈소스 파헤치기🪐
728x90
반응형

오랜만에 good first issue를 하나 잡았다. Password Strength Tester and Cracker라는 자바 콘솔 프로젝트인데, 비밀번호 강도를 판정해주는 기능에 버그가 있다는 이슈였다.

 

문제의 코드는 이거였다.

String specialChars = "!@#$%^&*()";

 

특수문자를 딱 10개만 인정한다. - _ [ ] { } 같은 건 전부 특수문자 취급을 못 받는다는 뜻이다. 그래서 고치기 전 판정이 이렇게 나온다.

비밀번호 판정
correct-horse-battery1A (23자) Medium
P@ssw0rd[]{} (12자) Strong

 

23자짜리 패스프레이즈는 Medium인데, 사전에 있는 password를 leet로 바꾼 12자짜리는 Strong이다. 그것도 뒤에 붙인 []{}는 인정도 못 받고 오직 @ 하나 덕분에. 거의 거꾸로다🥲


특수문자 10개만 인정하는 문제와 수정안이 적힌 GitHub 이슈


근데 이슈에 적힌 수정은 한 줄이었다

Invert the test. Rather than listing which symbols count,
treat anything that isn't a letter, digit, or whitespace as a symbol

 

if (!Character.isLetterOrDigit(ch) && !Character.isWhitespace(ch)) hasSpecial = true;

 

목록을 나열하지 말고 뒤집으라는 것. 이거 한 줄이면 끝나는 거 아닌가? 싶었는데 메인테이너가 이슈를 할당해주면서 코멘트를 남겼다.

 

The part worth spending your time on is the test cases rather than the one-line fix.
I'd rather see a PR that gets those judgements written down explicitly than one that just widens the character set.

 

특수문자 목록만 넓히는 PR은 원하지 않는다고. 대신 두 가지를 정하고 그 근거를 적으라고 했다.

  • 공백은 특수문자인가? 29자짜리 패스프레이즈가 지금은 "소문자만"으로 판정되는데, 그렇다고 공백을 "특수"라고 부르는 것도 이상하지 않나?
  • 길이만으로 충분한가? aaaaaaaaaaaaaaaaaaaa는 20자짜리 아무것도 아닌 문자열인데.

한 줄 고치는 이슈인 줄 알았더니, 사실은 뭘 강한 비밀번호로 볼 것인가를 정하는 이슈였던 것이다.


그래서 코드보다 규칙을 먼저 적었다

바로 코딩하지 않고 이슈에 코멘트부터 남겼다. 내가 제안하는 규칙은 이랬다.

classes = {대문자, 소문자, 숫자, 특수} 중 존재하는 개수
Strong  = 12자 이상 && classes >= 3
Medium  = (8자 이상 && classes >= 2) || (20자 이상 && classes >= 1)
Weak    = 그 외
오버라이드: 서로 다른 문자가 2개 이하면 무조건 Weak

 

두 질문에 대한 내 답은 규칙 안에 박아뒀다.

 

공백은 특수문자로 안 친다. 대신 길이가 패스프레이즈를 구제하게 했다. 공백을 기호라고 우기면 평범한 문장이 있지도 않은 기호 다양성을 가진 것처럼 보이게 되니까. correct horse battery staple은 "20자 이상"이라는 조항으로 Medium이 된다. 정직하게.

 

길이만으로는 부족하다. 그래서 "서로 다른 문자 2개 이하면 Weak" 가드를 넣었다. 사실 이 가드가 있어야 위의 길이 조항을 안심하고 쓸 수 있다. 없으면 aaaaaaaaaaaaaaaaaaaa가 20자라는 이유로 Medium이 되어버린다.

 

그리고 하나 승인을 요청했다. 기존 Strong 조건이 4종류 전부를 요구하는데, 이슈 표에 "Strong이어야 한다"고 적힌 my_secure_pass_2026!에는 대문자가 없다. 4종 전부는 애초에 성립할 수 없었다.


그리고 내가 판 함정에 내가 빠졌다💥

메인테이너 답변은 거의 전부 yes였다. 기분 좋게 읽어 내려가는데 중간에 이런 게 있었다.

 

One thing to catch before you code it
Your ruleset and one of your assumptions disagree.

 

...네?

 

내가 코멘트에 이렇게 썼었다. "표에서 비어있는 my_secure_pass_2026는 Medium으로 하겠습니다."

 

그런데 내가 방금 제안한 규칙에 넣고 돌려보면:

  • 길이 19
  • 소문자 ✓, 숫자 ✓, 그리고 _는... 내가 제안한 반전 테스트에서 특수문자다 ✓
  • 3종류 + 12자 이상 → Strong

밑줄을 깜빡한 거다. "영문·숫자·공백이 아니면 특수"라고 내 손으로 써놓고, 정작 그 줄을 계산할 때는 _를 안 세었다. 표의 "현재 판정" 칸에 적힌 Medium을 무심코 "기대 판정" 칸에 옮겨 적은 셈이었다. 심지어 그 Medium은 지금 고치려는 버그가 만들어낸 값인데😇

 

메인테이너의 마무리가 좋았다.

 

That's the whole point of writing the rules down first
— this is the kind of contradiction that's invisible until the cases are on the page.

규칙을 먼저 적어두는 게 바로 이래서라고. 케이스를 지면에 올려놓기 전엔 안 보이는 종류의 모순이라고. 창피하긴 했는데, 코드 다 짜고 나서 발견하는 것보단 백배 나았다.


내 규칙의 모순을 지적하고 테스트 작성을 요청한 메인테이너 코멘트


테스트가 곧 명세다

메인테이너가 요구한 마지막 조건은 이거였다.

That way the test is the spec, and if anyone later tweaks a threshold,
the row that changes tells them what they broke.

 

테스트 프레임워크 없이, failures 카운터와 평범한 if와 System.exit(1)만 쓰는 자체 검증 클래스를 만들었다. 이슈 표의 모든 행 + 내가 추가한 엣지 케이스를 기대값으로 박아넣었다.

expect("correct-horse-battery1A", "Strong");
expect("my_secure_pass_2026", "Strong");          // 밑줄이 특수 → 3종
expect("correct horse battery staple", "Medium"); // 길이가 구제, 공백은 기호가 아님
expect("aaaaaaaaaaaaaaaaaaaa", "Weak");           // 길이만으론 부족

 

그런데 여기서 하나 빠진 게 있었다. "공백은 특수문자가 아니다"라는 결정을 검증하는 케이스가 없었다. 패스프레이즈 케이스는 공백을 특수로 쳐도 어차피 Medium이 나와서, 둘을 구분해내지 못한다. 즉 나중에 누가 공백을 특수로 바꿔도 테스트가 안 깨진다.

 

그래서 한 줄 추가했다.

expect("abcdefgh 1234", "Medium"); // 공백을 특수로 치면 3종이 되어 Strong으로 바뀐다

 

13자에 소문자+숫자 2종이라 Medium. 만약 공백을 특수로 인정하면 3종이 되면서 Strong으로 튀고, 테스트가 깨진다. 이제 그 판단이 진짜로 코드에 고정된 것이다.


다만, 고친 뒤에도 P@ssw0rd[]{}는 Strong이다

솔직하게 짚어둘 게 있다. 새 규칙으로 돌려도 P@ssw0rd[]{}는 여전히 Strong이다. 12자에 네 종류를 다 갖췄으니 규칙상 그렇다. 하지만 이건 크래킹 툴이 제일 먼저 시도하는 password의 leet 변형이다. 실제 강도로 치면 최하위권인데 우리 검사기는 최고 등급을 준다.

 

이 검사기가 보는 건 문자 종류의 개수와 길이뿐이기 때문이다. 사전 단어도, a→@ o→0 같은 leet 치환도, 키보드 패턴도 전혀 모른다. 그래서 P@ssw0rd[]{}와 진짜 무작위 12자를 구분하지 못한다.

이번 작업이 고친 건 일관성이지 정확성이 아니었던 셈이다. 제대로 하려면 zxcvbn 같은 사전 기반 추정기가 필요한데, 그건 이 이슈의 범위를 한참 넘는 얘기다.


마무리

한 줄 고치는 이슈인 줄 알고 시작했는데, 실제 로직 변경은 13줄 추가 4줄 삭제였고 시간의 대부분은 "무엇을 강한 비밀번호로 볼 것인가"를 정하고 그 근거를 적는 데 썼다.

 

돌아보면 이번에 제일 많이 배운 건 자바 문법이 아니라 이거였다. 머릿속에서 맞는 것 같은 규칙도, 케이스를 늘어놓고 하나씩 돌려보기 전까진 모른다. 내 규칙과 내 기대값이 어긋나 있는 걸 나는 끝까지 못 봤고, 지적을 받고 나서야 종이 위에 늘어놓고 계산해봤다. 테스트를 명세로 남기라는 말이 왜 그렇게 강조됐는지 알 것 같았다.

 

Success⭐

728x90
반응형
'◈ Yermi Project/오픈소스 파헤치기🪐' 카테고리의 다른 글
  • [일상] Interview Question for Beginner에 기여하기✍🏻 [예비 개발자들 또는 개발자들의 기술 면접 준비를 위한 자료를 정리해놓은 저장소]
  • [일상] 브라우저 창 조절 시 이미지맵에서 NaN이 발생하다💥 [jQuery-rwdImageMaps, 오픈소스 파헤치기]
  • [일상] Github 오픈소스 첫 기여 후기 [MyBatis, contribute 진행과정 및 후기]
  • [일상] 주니어 개발자, Github 오픈소스에 첫 기여를 시도하다! [SQL 프레임워크 MyBatis에 기여하기]
예르미(yermi)
예르미(yermi)
끊임없이 제 자신을 계발하는 개발자입니다👨🏻‍💻
  • 예르미(yermi)
    예르미의 코딩노트
    예르미(yermi)
  • 전체
    오늘
    어제
    • 분류 전체보기 (1024) N
      • ◎ Java (133)
        • Java☕ (93)
        • JSP📋 (26)
        • Applet🧳 (6)
        • Interview👨🏻‍🏫 (8)
      • ◎ JavaScript (48)
        • JavaScript🦎 (25)
        • jQuery🌊 (8)
        • React🌐 (2)
        • Vue.js🔰 (6)
        • Node.js🫒 (3)
        • Google App Script🐑 (4)
      • ◎ HTML5+CSS3 (17)
        • HTML5📝 (8)
        • CSS3🎨 (9)
      • ──────────── (0)
      • ▣ Framework (67)
        • Spring🍃 (36)
        • Spring Boot🍀 (12)
        • Bootstrap💜 (3)
        • Selenium🌕 (6)
        • MyBatis🐣 (10)
      • ▣ Tools (47)
        • API🎯 (18)
        • Library🎲 (15)
        • JitPack🚀 (3)
        • Jenkins👨🏻 (7)
        • Thymeleaf🌿 (4)
      • ▣ Server (30)
        • Apache Tomcat🐱 (14)
        • Apache HTTP Server🛡️ (1)
        • Nginx🧶 (7)
        • OracleXE💿 (4)
        • VisualSVN📡 (4)
      • ▣ Infra & DevOps (19) N
        • LGTM Stack🔭 (5)
        • Kafka🐦‍🔥 (0)
        • Kubernetes🚢 (8)
        • KubeCon Japan 2026⚓ (6) N
      • ▣ OS : 운영체제 (18)
        • cmd : 명령프롬프트💻 (10)
        • Linux🐧 (8)
      • ▣ SQL : Database (56)
        • Oracle SQL🏮 (26)
        • PL SQL💾 (9)
        • MySQL🐬 (6)
        • MariaDB🦦 (6)
        • H2 Database🔠 (3)
        • SQL 실전문제🐌 (6)
      • ────────── (0)
      • ◈ Human Project (86)
        • Mini : Library Service📚 (15)
        • 화면 설계 [HTML]🐯 (10)
        • 서버 프로그램 구현🦁 (15)
        • Team : 여수어때🛫 (19)
        • Custom : Student🏫 (9)
        • Custom : Board📖 (18)
      • ◈ Yermi Project (49) N
        • 조사모아(Josa-moa)📬 (5)
        • Riddle-Game🧩 (6)
        • 맛있을 지도🍚 (2)
        • 어디 가! 박대리!🙋🏻‍♂️ (5)
        • 조크베어🐻‍❄️ (4)
        • Looks Like Thirty🦉 (2)
        • Toy Project💎 (12)
        • 오픈소스 파헤치기🪐 (5)
        • 오늘가챠🃏 (8) N
      • ◈ Refactoring (15)
        • Mini : Library Service📚 (8)
        • 서버 프로그램 구현🦁 (1)
        • Team : 여수어때🛫 (0)
        • 쿼리 튜닝일지🔧 (6)
      • ◈ Coding Test (80)
        • 백준(BOJ)👨🏻‍💻 (71)
        • 프로그래머스😎 (2)
        • 코드트리🌳 (7)
      • ◈ Study (129)
        • 기초튼튼 개발지식🥔 (25)
        • HTTP 웹 지식💡 (4)
        • 클린코드(Clean Code)🩺 (1)
        • 디자인패턴(GoF)🥞 (12)
        • 알고리즘(Algorithm)🎡 (14)
        • 다이어그램(Diagram)📈 (4)
        • 파이썬(Python)🐍 (16)
        • 에러노트(Error Note)🧱 (34)
        • 웹 보안(Web Security)🔐 (11)
        • 인공지능 AI🛸 (8)
      • ◈ 공부모임 (57)
        • 혼공학습단⏰ (18)
        • 코드트리 챌린지👊🏻 (2)
        • 개발도서 100독👟 (8)
        • 나는 리뷰어다🌾 (17)
        • 국가기술자격 서포터즈🌻 (12)
      • ◈ 자격증 공부 (48)
        • 정보처리기사🔱 (16)
        • 정보처리산업기사🔅 (9)
        • 정보보안기사⚜️ (11)
        • 컴퓨터활용능력 1급📼 (12)
      • ─────────── (0)
      • ◐ 기타 (124)
        • 알아두면 좋은 팁(tip)✨ (46)
        • 개발자의 일상🎈 (55)
        • 개발도서 서평🔍 (10)
        • 개발관련 세미나🎤 (2)
        • 블로그 꾸미기🎀 (9)
        • 사도신경 프로젝트🎚️ (2)
  • 인기 글

  • 최근 댓글

  • 반응형
    250x250
  • 태그

    일상
    Oracle
    코딩 테스트
    javascript
    BOJ
    Java
    자바스크립트
    CSS
    jsp
    spring
    꿀팁
    Error Note
    백준
    코딩
    SQL
    spring boot
    Project
    백준 티어
    Database
    프로그래밍
  • hELLO· Designed By정상우.v4.10.3
예르미(yermi)
[오픈소스] 특수문자를 10개만 아는 강도 검사기를 고치다🔐 [Password-Strength-Tester-and-Cracker]
상단으로

티스토리툴바