study-1/KeyStroke_Dynamics
0
1import gradio as ui2import numpy as np3import joblib4 5# 1. 기존 model.pt 파일 로드 (파이프라인)6try:7 pipeline = joblib.load('model.pt')8 scaler = pipeline['scaler']9 pca = pipeline['pca']10 svm = pipeline['svm']11 minmax = pipeline['minmax']12 gold_threshold = pipeline['best_threshold']13 target_subject = pipeline['target_subject']14except Exception as e:15 scaler, pca, svm, minmax = None, None, None, None16 gold_threshold = 0.5517 target_subject = "JunSeo"18 19# 2. 파이썬 백엔드 판정 로직 (초고속 모드)20def verify_real_keystroke(password, timing_data_str):21 if not password:22 return "<div style='background-color: #f5f5f5; padding: 15px; border-radius: 5px; color: #666;'>🔒 비밀번호를 입력해주세요.</div>", "인증 대기 중..."23 24 # 텍스트 검증25 if password != "sincos7475" and password != "signcos7475":26 return (27 "<div style='background-color: #fce8e6; padding: 15px; border-radius: 5px;'><h3 style='color: #c5221f; margin: 0;'>❌ [액세스 거부] 비밀번호 불일치</h3></div>",28 "비밀번호 텍스트가 틀렸습니다."29 )30 31 if not timing_data_str or timing_data_str == "WAIT":32 return "<div style='background-color: #feefe3; padding: 15px; border-radius: 5px; color: #b06000;'>⚠️ 입력 데이터 동기화 중입니다. 다시 엔터키를 눌러주세요.</div>", "데이터 추출 오류"33 34 try:35 # JS에서 넘겨준 H, DD, UD 값들을 실수형 배열로 파싱36 raw_features = [float(x) for x in timing_data_str.split(",") if x.strip() != ""]37 38 # 모델 호환을 위해 45개 피처 규격으로 자동 패딩/슬라이싱39 if len(raw_features) < 45:40 padding_val = raw_features[-1] if len(raw_features) > 0 else 0.1541 raw_features += [padding_val] * (45 - len(raw_features))42 elif len(raw_features) > 45:43 raw_features = raw_features[:45]44 45 features_np = np.array(raw_features).reshape(1, -1)46 47 # 스케일러 -> PCA -> SVM 파이프라인 즉시 통과48 scaled_data = scaler.transform(features_np)49 pca_data = pca.transform(scaled_data)50 raw_score = svm.decision_function(pca_data)51 scaled_score = minmax.transform(raw_score.reshape(-1, 1)).flatten()[0]52 53 # 최종 보안 스크리닝54 if scaled_score >= gold_threshold:55 status = f"✅ [액세스 승인] '{target_subject}' 본인 행동 패턴 일치"56 color_md = f"<div style='background-color: #e6f4ea; padding: 15px; border-radius: 5px; border-left: 5px solid green;'><h3 style='color: green; margin: 0;'>{status}</h3><p style='margin: 5px 0 0 0; color: #333; font-size: 0.95em;'>H, DD, UD 타이핑 동역학이 완벽합니다.</p></div>"57 else:58 status = f"⚠️ [액세스 거부] 도용 의심 (타이핑 리듬 불일치)"59 color_md = f"<div style='background-color: #feefe3; padding: 15px; border-radius: 5px; border-left: 5px solid #b06000;'><h3 style='color: #b06000; margin: 0;'>{status}</h3><p style='margin: 5px 0 0 0; color: #333; font-size: 0.95em;'>비밀번호는 일치하나 타건 속도와 간격이 본인과 다릅니다.</p></div>"60 61 score_info = f"■ H/DD/UD 추출 피처: {len(timing_data_str.split(','))}개\n■ 패턴 매칭 점수: {scaled_score:.4f}\n■ 요구 커트라인: {gold_threshold:.4f}"62 return color_md, score_info63 64 except Exception as e:65 return f"<div style='background-color: #feefe3; padding: 15px; border-radius: 5px; color: orange;'>연산 오류: {str(e)}</div>", "다시 시도해주세요."66 67# 3. 사용자 제공 HTML 로직을 완벽히 이식한 Gradio용 커스텀 JS68js_enter_trigger = """69function() {70 let currentEvents = [];71 72 setTimeout(function() {73 const pwd_input = document.querySelector("#pwd_input input");74 const hidden_output = document.querySelector("#hidden_timing_input textarea, #hidden_timing_input input");75 const hidden_submit_btn = document.querySelector("#hidden_submit_btn");76 77 if (!pwd_input) return;78 79 pwd_input.addEventListener('keydown', function(e) {80 // 엔터키 입력 시 H, DD, UD 즉시 계산 후 백엔드 전송 트리거81 if (e.key === "Enter") {82 e.preventDefault();83 84 if (currentEvents.length > 0) {85 const downs = currentEvents.map(ev => ev.down);86 const ups = currentEvents.map(ev => ev.up !== null ? ev.up : performance.now());87 88 // H, DD, UD 계산 (초 단위 변환)89 const H = downs.map((d, i) => (ups[i] - d) / 1000.0);90 const DD = downs.slice(1).map((d, i) => (d - downs[i]) / 1000.0);91 const UD = downs.slice(1).map((d, i) => (d - ups[i]) / 1000.0);92 93 const features = [...H, ...DD, ...UD];94 95 if (hidden_output) {96 hidden_output.value = features.join(",");97 hidden_output.dispatchEvent(new Event('input', { bubbles: true }));98 }99 100 // 0.05초 대기 후 보이지 않는 버튼을 JS가 강제 클릭하여 딜레이 0 구현101 if (hidden_submit_btn) {102 setTimeout(() => { hidden_submit_btn.click(); }, 50);103 }104 }105 return;106 }107 108 // 백스페이스나 딜리트 시 초기화 로직 (HTML 코드 반영)109 if (e.key === "Backspace" || e.key === "Delete") {110 currentEvents = [];111 return;112 }113 114 if (e.ctrlKey || e.metaKey || e.altKey) return;115 116 if (e.key.length === 1) {117 currentEvents.push({118 key: e.key,119 down: performance.now(),120 up: null121 });122 }123 });124 125 pwd_input.addEventListener('keyup', function(e) {126 if (e.ctrlKey || e.metaKey || e.altKey) return;127 128 if (e.key.length === 1) {129 const t = performance.now();130 for (let i = currentEvents.length - 1; i >= 0; i--) {131 if (currentEvents[i].key === e.key && currentEvents[i].up === null) {132 currentEvents[i].up = t;133 break;134 }135 }136 }137 });138 139 pwd_input.addEventListener('paste', function(e) {140 e.preventDefault();141 currentEvents = [];142 });143 144 }, 1000);145}146"""147 148with ui.Blocks(theme="soft") as demo:149 ui.Markdown("# ⚡ 무지연 생체 인증 시스템 (H, DD, UD 정밀 추출)")150 ui.Markdown("제공된 정밀 타이밍 수집 로직 적용 완료. 비밀번호를 타이핑한 후 **`Enter(엔터)` 키를 누르면 즉시 결과를 판정**합니다.")151 152 with ui.Row():153 with ui.Column():154 pwd_box = ui.Textbox(155 label="비밀번호 입력 후 [Enter] (테스트: sincos7475)", 156 placeholder="입력 후 엔터키를 치면 바로 넘어갑니다.",157 type="password",158 elem_id="pwd_input"159 )160 161 # JS 데이터를 주고받기 위한 히든 필드와 히든 버튼162 hidden_timing = ui.Textbox(visible=False, value="", elem_id="hidden_timing_input")163 hidden_btn = ui.Button("숨겨진 제출 버튼", visible=False, elem_id="hidden_submit_btn")164 165 with ui.Column():166 result_html = ui.HTML(label="보안 판정 결과", value="<div style='background-color: #f5f5f5; padding: 15px; border-radius: 5px; color: #666;'>대기 중...</div>")167 score_box = ui.Textbox(label="패턴 데이터 분석 지표", interactive=False)168 169 # 버튼 클릭 이벤트를 큐잉 없이 즉각 처리 (queue=False)170 hidden_btn.click(171 fn=verify_real_keystroke,172 inputs=[pwd_box, hidden_timing],173 outputs=[result_html, score_box],174 queue=False 175 )176 177 demo.load(fn=None, inputs=None, outputs=None, js=js_enter_trigger)178 179if __name__ == "__main__":180 demo.launch()181 