공인 인증서 자체는 2020년 12월경 사라졌지만, 정말 끈질기게도 은행들은 계속 쓰더라..
하루에도 수십번 공인인증서 로그인을 해야 하는 사람들이라면 로그인 절차는 정말 고역일 것이다.
한번 자동화 해보자!
USB 지원 관점에서, Arduino Uno는 별도의 USB-Serial 변환기를 사용하지만, Leonardo는 마이크로컨트롤러 자체에서 USB 기능을 지원해 키보드나 마우스처럼 인식된다!
고로 이때 계획한 것은 다음과 같았다.
#include <EEPROM.h>
#include <Keypad.h>
#include <Keyboard.h>
const byte ROWS = 4; // 경(rows) 개수
const byte COLS = 4; // 열(columns) 개수
int d = 70;
char keys[ROWS][COLS] = {
{'G','F','E','D'},
{'C','B','A','9'},
{'8','7','6','5'},
{'4','3','2','1'}
};
byte rowPins[ROWS] = {6, 7, 8, 9}; // R1, R2, R3, R4 단자가 연결된 아두이노 핀 번호
byte colPins[COLS] = {5, 4, 3, 2}; // C1, C2, C3, C4 단자가 연결된 아두이노 핀 번호
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// 비밀번호 저장 공간
const int passwordLength = 32;
char passwords[16][passwordLength];
// EEPROM 주소 지정
int eepromAddresses[16] = {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480};
void loadPasswordsFromEEPROM();
void savePasswordToEEPROM(int index, const char* password);
int getPasswordIndex(char key);
void processCommand(String command);
void typeWithPressRelease(String str, int delayTime = 150);
void setup() {
Serial.begin(9600);
Keyboard.begin();
loadPasswordsFromEEPROM();
}
void loop() {
if (Serial.available()) {
String command = Serial.readStringUntil('\n');
processCommand(command);
}
char key = keypad.getKey();
if (key) {
Serial.println(key);
int index = getPasswordIndex(key);
if (index != -1) {
typeWithPressRelease(passwords[index], d);
}
}
}
// 비밀번호를 EEPROM에서 불러오기
void loadPasswordsFromEEPROM() {
for (int i = 0; i < 16; i++) {
for (int j = 0; j < passwordLength; j++) {
passwords[i][j] = EEPROM.read(eepromAddresses[i] + j);
}
}
}
// 비밀번호를 EEPROM에 저장하기
void savePasswordToEEPROM(int index, const char* password) {
for (int i = 0; i < passwordLength; i++) {
EEPROM.write(eepromAddresses[index] + i, password[i]);
}
}
// 키팩 입력에 따른 비밀번호 인덱스 반환
int getPasswordIndex(char key) {
if (key >= '1' && key <= '9') {
return key - '1';
} else if (key >= 'A' && key <= 'G') {
return key - 'A' + 9;
}
return -1;
}
// 시리얼 통신을 통한 명령 처리
void processCommand(String command) {
if (command.startsWith("SET")) {
int index = command.substring(4, 5).toInt();
String newPassword = command.substring(6);
newPassword.toCharArray(passwords[index], passwordLength);
savePasswordToEEPROM(index, passwords[index]);
Serial.println("PASSWORD SET");
} else if (command.startsWith("GET")) {
int index = command.substring(4, 5).toInt();
Serial.println(passwords[index]);
} else if (command == "RESET") {
for (int i = 0; i < 16; i++) {
const char* defaultPassword = "default";
savePasswordToEEPROM(i, defaultPassword);
}
Serial.println("PASSWORD RESET");
} else if (command == "PING") {
Serial.println("PONG");
}
}
// 키보드 입력 함수
void typeWithPressRelease(String str, int delayTime) {
for (int i = 0; i < str.length(); i++) {
Keyboard.press(str[i]); // 키 누르기
delay(delayTime / 2); // 약간의 대기
Keyboard.release(str[i]); // 키 떼기
delay(delayTime); // 약간 더 대기
}
}
const byte ROWS = 4; // 경(rows) 개수
const byte COLS = 4; // 열(columns) 개수
char keys[ROWS][COLS] = {
{'G','F','E','D'},
{'C','B','A','9'},
{'8','7','6','5'},
{'4','3','2','1'}
};
byte rowPins[ROWS] = {6, 7, 8, 9}; // R1, R2, R3, R4 단자가 연결된 아두이노 핀 번호
byte colPins[COLS] = {5, 4, 3, 2}; // C1, C2, C3, C4 단자가 연결된 아두이노 핀 번호
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
int eepromAddresses[16] = {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480};
// 비밀번호 저장 공간
const int passwordLength = 32;
char passwords[16][passwordLength];
// 비밀번호를 EEPROM에서 불러오기
void loadPasswordsFromEEPROM() {
for (int i = 0; i < 16; i++) {
for (int j = 0; j < passwordLength; j++) {
passwords[i][j] = EEPROM.read(eepromAddresses[i] + j);
}
}
}
// 비밀번호를 EEPROM에 저장하기
void savePasswordToEEPROM(int index, const char* password) {
for (int i = 0; i < passwordLength; i++) {
EEPROM.write(eepromAddresses[index] + i, password[i]);
}
}
// 키팩 입력에 따른 비밀번호 인덱스 반환
int getPasswordIndex(char key) {
if (key >= '1' && key <= '9') {
return key - '1';
} else if (key >= 'A' && key <= 'G') {
return key - 'A' + 9;
}
return -1;
}
void processCommand(String command) {
if (command.startsWith("SET")) {
int index = command.substring(4, 5).toInt();
String newPassword = command.substring(6);
newPassword.toCharArray(passwords[index], passwordLength);
savePasswordToEEPROM(index, passwords[index]);
Serial.println("PASSWORD SET");
} else if (command.startsWith("GET")) {
int index = command.substring(4, 5).toInt();
Serial.println(passwords[index]);
} else if (command == "RESET") {
for (int i = 0; i < 16; i++) {
const char* defaultPassword = "default";
savePasswordToEEPROM(i, defaultPassword);
}
Serial.println("PASSWORD RESET");
}
}
// 키보드 입력 함수
void typeWithPressRelease(String str, int delayTime) {
for (int i = 0; i < str.length(); i++) {
Keyboard.press(str[i]); // 키 누르기
delay(delayTime / 2); // 약간의 대기
Keyboard.release(str[i]); // 키 떠기
delay(delayTime); // 약간 더 대기
}
}
leonardo.build.vid=0x046D
leonardo.build.pid=0xC31C
leonardo.build.usb_product="Logitech K120"
import sys
import serial
import serial.tools.list_ports
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QComboBox, QTextEdit, QSpinBox
class PasswordManager(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.serial_connection = None
def initUI(self):
# 메인 레이아웃
layout = QVBoxLayout()
# 시리얼 포트 선택 섹션
port_layout = QHBoxLayout()
self.port_label = QLabel('시리얼 포트 선택:')
self.port_combo = QComboBox()
self.port_refresh_btn = QPushButton('포트 새로고침')
self.port_refresh_btn.clicked.connect(self.refresh_ports)
port_layout.addWidget(self.port_label)
port_layout.addWidget(self.port_combo)
port_layout.addWidget(self.port_refresh_btn)
# 연결 버튼
self.connect_btn = QPushButton('연결')
self.connect_btn.clicked.connect(self.connect_serial)
port_layout.addWidget(self.connect_btn)
layout.addLayout(port_layout)
# 비밀번호 설정 섹션
password_layout = QHBoxLayout()
self.password_input = QLineEdit(self)
self.password_input.setPlaceholderText("새 비밀번호 입력")
self.password_index_spinbox = QSpinBox(self)
self.password_index_spinbox.setRange(1, 16)
self.password_set_btn = QPushButton("비밀번호 설정")
self.password_set_btn.clicked.connect(self.set_password)
password_layout.addWidget(QLabel("비밀번호 인덱스:"))
password_layout.addWidget(self.password_index_spinbox)
password_layout.addWidget(self.password_input)
password_layout.addWidget(self.password_set_btn)
layout.addLayout(password_layout)
# 비밀번호 불러오기 버튼
self.password_get_btn = QPushButton("비밀번호 불러오기")
self.password_get_btn.clicked.connect(self.get_password)
layout.addWidget(self.password_get_btn)
# 비밀번호 초기화 버튼
self.password_reset_btn = QPushButton("비밀번호 초기화")
self.password_reset_btn.clicked.connect(self.reset_passwords)
layout.addWidget(self.password_reset_btn)
# 상태 메시지 출력 창
self.status_output = QTextEdit(self)
self.status_output.setReadOnly(True)
layout.addWidget(self.status_output)
# 메인 레이아웃 설정
self.setLayout(layout)
self.setWindowTitle('아두이노 비밀번호 관리자')
self.refresh_ports()
# 시리얼 포트 검색
def refresh_ports(self):
self.port_combo.clear()
ports = serial.tools.list_ports.comports()
for port in ports:
self.port_combo.addItem(port.device)
# 시리얼 포트 연결
def connect_serial(self):
selected_port = self.port_combo.currentText()
try:
self.serial_connection = serial.Serial(selected_port, 9600, timeout=1)
self.status_output.append(f"{selected_port}에 연결되었습니다.")
except Exception as e:
self.status_output.append(f"연결 실패: {str(e)}")
# 비밀번호 설정
def set_password(self):
if self.serial_connection:
new_password = self.password_input.text()
index = self.password_index_spinbox.value() - 1
if len(new_password) > 0:
command = f"SET {index} {new_password}\n"
self.serial_connection.write(command.encode())
self.status_output.append(f"인덱스 {index + 1}에 비밀번호가 설정되었습니다.")
else:
self.status_output.append("비밀번호를 입력해주세요.")
else:
self.status_output.append("시리얼 연결이 없습니다.")
# 비밀번호 불러오기
def get_password(self):
if self.serial_connection:
index = self.password_index_spinbox.value() - 1
command = f"GET {index}\n"
self.serial_connection.write(command.encode())
response = self.serial_connection.readline().decode().strip()
self.status_output.append(f"인덱스 {index + 1}의 비밀번호: {response}")
else:
self.status_output.append("시리얼 연결이 없습니다.")
# 비밀번호 초기화
def reset_passwords(self):
if self.serial_connection:
command = "RESET\n"
self.serial_connection.write(command.encode())
self.status_output.append("모든 비밀번호가 초기화되었습니다.")
else:
self.status_output.append("시리얼 연결이 없습니다.")
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = PasswordManager()
ex.show()
sys.exit(app.exec_())