일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- ios
- RN새로운아키텍쳐
- CS
- RN아키텍쳐
- async
- Promise
- react-native-image-picker
- react native
- react-native
- await
- axios
- Swift
- rn
- react animation
- React-Native업데이트
- private-access-to-photos
- no-permission-handler-detected
- 비동기
- named type
- promise.all
- javascript
- react
- motion.div
- hydration mismatch
- react-native-permissions
- debounce
- animation
- RN업데이트
- Throttle
- Hash-table
- Today
- Total
하루살이 개발일지
생체인증을 통한 전자서명 (RSA 암호화와 SHA-256 해싱 알고리즘) 본문
현대 디지털 세계에서는 데이터 보안이 무엇보다 중요하다. 서버는 민감한 정보를 클라이언트에 무분별하게 제공할 수 없기 때문에, 정보를 검증하거나 안전하게 전달하기 위해 디지털 서명 방식이 필수적이다. 이를 위해 널리 사용되는 두 가지 핵심 기술이 바로 RSA 암호화와 SHA-256 해싱 알고리즘이다.
프로젝트 당시 재화가 오고가는 기능을 구현하는 과정 중 본인인증이 필요한 요구사항이 있었다. 앱플로우 상 꽤나 빈번하게 발생할 검증 과정이었기 때문에, UX를 위해 PASS인증이나 카카오톡 인증 방식 등을 사용하기에는 번거로울 거라고 판단하였다. 따라서 생체인증을 활용한 전자서명 방식을 채택하였고, 이를 구현하는 과정에서 RSA와 SHA-256에 대한 개념을 접했다.
RSA와 SHA-256이 보안과 무결성을 보장하는 데 어떤 역할을 하는지 구체적으로 설명하고자 한다. 생체인증뿐만 아니라 블록체인, 디지털 서명과 같은 다양한 보안 시스템에서도 이 기술들은 핵심 요소로 사용된다고 한다. 이 글에서는 특히 RSA에 중점을 두고 그 작동 원리를 분석해 보고자 한다.
1. RSA: 비대칭키 암호화 알고리즘
RSA는 비대칭키 암호화를 기반으로 한 알고리즘으로, 공개키와 개인키(=비밀키)를 이용해 데이터를 암호화하고 복호화한다. 이 과정에서 SHA-256 같은 해싱 알고리즘이 함께 사용되며, 데이터의 무결성을 검증한다.
RSA의 작동 원리
RSA는 크게 다음과 같은 과정으로 작동한다:
1. 키 생성
- 두 개의 큰 소수를 기반으로 공개키와 개인키를 생성한다.
- 공개키는 누구나 볼 수 있으며 데이터를 암호화하는 데 사용된다.
- 개인키는 비밀로 유지되며 암호화된 데이터를 복호화하는 데 사용된다.
2. 암호화
- 평문(보내고자 하는 메시지)을 공개키를 사용하여 암호화한다.
- 암호화된 데이터(=서명)는 수신자에게 전송된다.
3. 복호화
- 수신자는 개인키를 사용하여 암호화된 데이터를 복호화한다.
- 복호화를 통해 원래의 메시지(평문)를 복원할 수 있다.
RSA와 챌린지 데이터 검증
서버에서 클라이언트의 데이터를 검증할 때, 챌린지 데이터가 추가로 활용될 수 있다. 이 과정은 다음과 같다:
- 서버가 클라이언트에게 고유한 챌린지 데이터를 생성하여 전송한다.
- 클라이언트는 이 챌린지 데이터를 포함해 메시지를 구성한 후, SHA-256으로 해싱하여 해시 값을 생성한다.
- 클라이언트는 해시 값을 RSA 개인키로 암호화해 디지털 서명을 생성하고 서버로 전송한다.
- 서버는 공개키로 서명을 복호화하고, 챌린지 데이터를 포함한 원본 메시지를 해싱하여 결과를 비교한다.
- 두 해시 값이 일치하면 검증에 성공한다.
예시: 사용자가 "LoginRequest"라는 메시지와 함께 챌린지 데이터 "12345"를 받았다면, 이 두 데이터를 결합해 SHA-256 해싱을 수행한다. 클라이언트는 RSA 개인키로 서명을 생성해 전송하며, 서버는 공개키를 이용해 이를 확인하고 무결성을 검증한다.
RSA의 활용 사례
- 전자 서명: 메시지가 변조되지 않았음을 증명.
- SSL/TLS: 안전한 웹 통신 보장.
- 보안 데이터 전송: 민감한 데이터를 안전하게 공유.
2. SHA-256: 데이터의 무결성을 보장하는 해싱 알고리즘
SHA-256은 데이터를 고정된 크기의 고유한 해시 값으로 변환하는 알고리즘이다. 이 과정에서 입력 데이터의 무결성을 보장하며, RSA와 결합해 디지털 서명 생성에 사용된다.
SHA-256의 작동 원리
- 데이터 분할: 입력 데이터를 512비트 블록 단위로 분할한다.
- 초기 해시 값 설정: 고정된 256비트 초기 해시 값을 설정한다.
- 압축 함수 반복: 각 블록을 처리하며 복잡한 비트 연산과 압축 함수를 반복 적용한다.
- 최종 출력: 고유한 256비트 해시 값이 생성된다.
예시: "Hello, World!"라는 메시지를 입력하면, SHA-256은 다음과 같은 고유한 해시 값을 출력한다:
a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b5e6cce00bf9f52d1
만약 메시지의 한 글자라도 변경된다면, 완전히 다른 해시 값이 생성된다.
SHA-256의 특징
- 단방향성: 해시 값에서 원본 데이터를 역추적할 수 없다.
- 변경 감지: 입력 데이터가 조금이라도 바뀌면 완전히 다른 해시 값을 생성한다.
- 충돌 회피: 동일한 해시 값을 가지는 서로 다른 입력 데이터를 찾기 매우 어렵다.
SHA-256의 활용 사례
- 블록체인: 데이터 블록 간 무결성을 유지.
- 비밀번호 저장: 비밀번호를 직접 저장하지 않고 해시 값으로 변환.
- 디지털 서명: 메시지의 무결성을 증명.
3. 생체인증과 전자서명에서의 활용
생체인증은 RSA와 SHA-256을 활용하여 더 높은 수준의 보안을 제공한다. 사용자의 고유 생체 정보를 활용해 데이터 무결성을 보장하고, 인증 과정을 간소화한다. 여기에 챌린지 데이터를 포함함으로써 클라이언트-서버 간 통신의 신뢰성을 더욱 강화할 수 있다.
생체인증 기반 전자서명의 흐름
- 사용자의 지문이나 얼굴 데이터를 입력받는다.
- 챌린지 데이터와 결합하여 SHA-256으로 해싱하여 고유한 해시 값을 생성한다.
- RSA 개인키로 이 해시 값을 암호화해 디지털 서명을 생성한다.
- 서버는 챌린지 데이터를 포함한 원본 데이터를 해싱하고, RSA 공개키로 서명을 검증하여 데이터의 무결성을 확인한다.
예시: 사용자가 모바일 앱에서 로그인할 때 지문을 스캔하고 서버에서 받은 챌린지 데이터를 조합한다. 이 데이터를 해싱한 후 RSA 개인키로 암호화하여 전송하면, 서버는 공개키를 통해 서명을 검증하고 사용자 인증을 완료한다.
4. 구현 방식
아래 코드는 생체인증 라이브러리인 react-native-biometrics 를 이용해 생체인증을 통한 전자서명 방식을 구현한 커스텀 훅이다.
import ReactNativeBiometrics, { BiometryTypes } from "react-native-biometrics";
import { Toast } from "react-native-toast-notifications";
export const useBiometricsAuthentication = () => {
const rnBiometrics = new ReactNativeBiometrics({
allowDeviceCredentials: true,
});
//생체인증 가능한지 확인하는 함수
const checkIsBiometricsAvailable = async () => {
const result = await rnBiometrics.isSensorAvailable();
if (!result) {
return { type: BiometryTypes.Biometrics, isAvailable: false };
}
const { biometryType } = result;
const isAvailable = [
BiometryTypes.FaceID,
BiometryTypes.TouchID,
BiometryTypes.Biometrics,
].includes(biometryType || "");
return {
type: biometryType || BiometryTypes.Biometrics,
isAvailable,
};
};
//서명 없이 생체인증하고 싶을때
const authenticateWithoutSignature = async () => {
const result = await rnBiometrics.simplePrompt({ //생체인증 트리거
promptMessage: "생체인증을 진행해주세요.",
});
return { success: result?.success || false };
};
//createKeys()를 사용하면 RSA로 키를 만들어줌
const createKeyPair = async () => {
const result = await rnBiometrics.createKeys();
if (result?.publicKey) {
return { success: true, publicKey: result.publicKey };
}
return { success: false, publicKey: null }; //public key만 알수있고 비밀키는 안전한 곳에 저장됨
};
const handleKeyPairCreation = async () => {
const authSuccess = await authenticateWithoutSignature();
if (authSuccess.success) {
const keyPair = await createKeyPair();
if (keyPair.success) {
Toast.show({ type: "success", text: "키페어 생성 성공" });
console.log("키페어 생성 성공, 이후 인증 시작");
await handleSignatureCreation();
} else {
Toast.show({ type: "error", text: "키페어 발급 실패" });
}
} else {
Toast.show({ type: "error", text: "생체 인증 실패" });
}
};
//키페어 삭제 함수
const resetKeystore = async () => {
const keysExist = await rnBiometrics.biometricKeysExist();
if (keysExist?.keysExist) {
const keysDeleted = await rnBiometrics.deleteKeys();
if (keysDeleted?.keysDeleted) {
Toast.show({ type: "success", text: "키페어 삭제 성공" });
} else {
Toast.show({ type: "error", text: "키페어 삭제 실패" });
}
}
};
const handleSignatureCreation = async () => {
const signatureResult = await rnBiometrics.createSignature({
promptMessage: "로그인을 위해 생체인증을 진행해주세요.",
payload: `myPayload`, //이곳에 보통 챌린지데이터를 넣음
});
if (signatureResult?.success) {
Toast.show({ type: "success", text: "서명 생성 성공." });
} else {
Toast.show({ type: "error", text: "서명 생성 실패." });
}
};
//생체인증 할 곳에서 호출할 함수
const checkBiometricAuthentication = async () => {
const biometrics = await checkIsBiometricsAvailable();
if (!biometrics.isAvailable) {
Toast.show({ type: "error", text: "생체인증 사용 불가능" });
return;
}
const keysExist = await rnBiometrics.biometricKeysExist();
if (!keysExist?.keysExist) {
await handleKeyPairCreation(); //없으면 키 생성
} else {
await handleSignatureCreation(); //있으면 서명 생성
}
};
return {
checkBiometricAuthentication,
resetKeystore,
createKeyPair,
};
};
💭 생체인증 말고 간편비밀번호 등을 활용하는 방법은 없을까? 더보기...
React-native-biometrics 라이브러리에서 제공해주는 메소드(키페어 생성, 서명 생성)를 직접 구현하면 생체인증 이외의 수단으로도 전자서명을 구현할 수 있다고 생각했다. 전자서명을 생성하는 과정은 굵게 다음과 같은 과정이 포함되는데,
1. 키 생성
2. 암호화를 통한 서명 생성
3. 서명 검증 (이 과정은 서버가 담당)
1, 2를 구현하기 위해 react-native-quick-crypto 라이브러리를 활용하였다.
이 라이브러리는 RSA와 SHA-256 알고리즘을 구현해 놓은 구현체이다.
코드로 구현하면 다음과 같다.
1. 키 생성
import crypto from "react-native-quick-crypto";
//키 생성하는 함수
const generateRSAKeyPair = () => {
const keypair = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
return keypair;
};
2. 암호화를 통한 서명 생성
// 데이터 서명 함수
const signData = (data: Buffer, privateKey: string) => {
try {
// SHA256 알고리즘으로 Sign 객체 생성
const sign = crypto.createSign("SHA256");
sign.update(data); // 서명에 포함할 데이터 업데이트
// 비밀키로 서명 생성
const signature = sign.sign(
{
key: privateKey,
format: "pem", // 비밀키 형식
type: "pkcs8", // 비밀키 타입
},
"base64"
); // 결과를 Base64로 반환
return signature;
} catch (error) {
return null;
}
};
이렇게 하면 어떤 수단으로도 데이터 전자서명 방식을 구현할 수 있게 된다!
5. 결론
RSA와 SHA-256은 현대 보안 기술에서 중요한 역할을 한다. RSA는 데이터를 안전하게 암호화하고 복호화하며, SHA-256은 데이터의 무결성을 보장한다. 이 두 기술은 생체인증 시스템과 결합하여 강력한 보안성과 편의성을 제공한다.
생체인증 기반 전자서명은 다음과 같은 장점을 제공한다:
- 보안 강화: 생체 정보는 복제하기 어렵다.
- 데이터 무결성 보장: SHA-256을 사용해 데이터 변조를 방지한다.
- 편의성 제공: 비밀번호 대신 생체 데이터를 사용하여 인증한다.
- 신뢰성 향상: 챌린지 데이터를 포함하여 통신 과정의 신뢰도를 보장한다.
'앱개발 > React-Native' 카테고리의 다른 글
Expo-router 의 개념과 사용 (0) | 2025.02.02 |
---|---|
Git 워크플로우 및 커밋/푸시 컨벤션 설정 (husky + commitizen) (2) | 2024.12.30 |
React Native Reanimated에 대해서 (4) | 2024.09.01 |
[RN] Deep Link란? (0) | 2024.06.13 |
React Native Reanimated란? (0) | 2024.04.17 |