대칭형:암호화 복호화 할 때 사용하는 키가 동일한 키 1개를 사용한다.
암호화 한 사람과 수신하는 사람이 같은키를 사용한다.
비대칭형:사용하는 키와 복호화 할 때 사용하는 키가 다른것. 키 2개
다른 사람에게 공개되어서는 절대 안되는 키(비밀 키) 비밀키를 토대로 만든 공개키 한쌍
타원 곡선 수학을 기반으로 디지털 서명을 보호하기 위해 사용하는 암호화 기술
공개키와 개인키의 개념이 있고, 공개키는 암호화와 검증에 사용, 개인키는 복호화 서명을만들 때 사용된다.
디지털 서명은 본인이 개인키로 메시지를 서명을 할 수 있고 공개키는 받은사람이 누구나 서명을 확인해 볼수있다.
쉽게 말해서 암호화 및 해독에 사용
다른사람의 공개키로 암호화해서 전달을 하면 그 사람의 개인키로만 복호화가 가능하고
당사자끼리의 데이터 기밀성 보장
디지털 서명을 본인의 개인키로 생성하고 다른 사람한테 전달해서 공개키와 함께 서명을 검증할 수 있는 무결성 보장
y^2=x^3+ax+b
기준점이 있고 기준점에서 개인키를 가지고 공개키를 구하는데 기준점과 공개키로 개인키를 역산하기 힘들다. 그냥 그렇다.
// 지갑 구성
// 개인키 ,공개키, 서명
// 지갑주소 /계정 만들기
// 개인키와 공개키와 서명을 이용한 신원 인증 방식은 분산원장이라는 이해가 필요
// 분산원장 : 장부를 모두가 관리하는것. 데이터의 합의 기술
// crypto.elliptic,crypto-js
// npm i -D crypto
// npm i -D elliptic @types/elliptic
// npm i -D crypto-js @types/crypto-js
import { randomBytes } from "crypto";
import elliptic from "elliptic";
import { SHA256 } from "crypto-js";
const ec = new elliptic.ec("secp256k1");
// secp256k1은 비트코인과 이더리움에서 사용되는 알고리즘
// 키 생성 및 디지털 서명 (너가 이걸 한게 맞는지 검증하기 위해 영수증),주소 생성
// 타원 곡선의 별명
// 전달하는 사람과 받는사람등 모든 사람들은 공통적으로 타원곡선의 한점을 알고있어야 하는데
// 이 점을 타원곡선의 기준점 G라고 부른다.
// 타원곡선의 기준점 좌표가 뭐냐에 따라 타원곡선에 별명을 붙여준다.
// 비트코인과 이더리움에서 사용되는 타원곡선 별명은 secp256k1
// y^2=x^2 + ax + b
// 이 방정식에 만족하는 곡선
// a가 0, b가 7 기준점 G는 x및 y좌표를 16진수로 표현한 것
// y^2=x^3+7
// A가 트랜잭션 만들고 서명을 만들고(영수증)
// 본인들 볼펜이 하나씩 있어서(개인키)
// 볼펜 준비 타원 곡선의 지정 범위내의 값으로 정한다(1~ n-1 까지의 정수 범위)(범위내의 정수).
// secp256k1 의 n은 1.157920892373162e+77값(과학 표기법)
// 위 수를 16진수로 변화한 값에 -1한 값이 n-1임
// 이 범위중에 하나를 사용해야함
// 개인키를 하나 임시로 지정을 해보면
// 전자 서명을 만들 때
// 2개의 서명이 필요하다.
// 서명 r:트랜잭션을 보낼 때 개인키 처럼 정수를 뽑아서 이 값을 k라고 한다. 서명 r=k*G(기준점)
// 서명 s: s=k^-1 ×(z+r×개인키) mod n k를 역수 계산 z+r* 게인키 나머지 n
// k=서명 r을 만들 때 뽑은 랜덤값
// z=만든 트랜잭션 정보
// r=서명 r
// 개인키 =범윙에서 지정하고 본인만 알고있는 개인키
// mod n=n으로 나눈 나머지
// 중요한건 서명 s를 만드는데 개인키를 사용했다는 개념
// w 동일한 서명을 만들지 않기 위해서 임의의 값을 추가 nonce값이라 보면됨
// U1=z*w mod n
// U2=r * w mod n
// U1 *G+U2 공개키 값을 구해서 서명 r과 같은지 비교해서 검증
// 이거를 전부 이해할 필요가 없고
// 중요한 중심만 이해를 하면된다. 개인키로 서명을 만든거고
// 이 서명을 가지고 공개키를 통해서 서명을 검증 할 수 있다.
// 데이터 전송
// 1. 트랜잭션 생성
// 2.개인키 생성
// 3.서명 r, 서명 s생성
// 데이터 수신
// U1 *G+U2 공개키 이 식으로 값을 구해서 서명 r과 비교(검증)
describe("지갑 만들기", () => {
let priveKey: string;
let publicKey: string;
let signature: elliptic.ec.Signature;
it("개인키 생성", () => {
// 2진수의 랜덤값을 만들자
// 16진수로 나타냄
priveKey = randomBytes(32).toString("hex");
console.log("개인키:" + priveKey);
// 개인키의 길이 64
console.log("개인키의 길이:" + priveKey.length);
});
// false 문자열 압축 여부 중요하지 않음
// 개인키로 공개키를 생성
it("공개키 생성", () => {
const keyPair = ec.keyFromPrivate(priveKey);
publicKey = keyPair.getPublic().encode("hex", false);
console.log("공개키:", publicKey);
// 공개키는 130자의 문자열
console.log("공개키의 길이:", publicKey.length);
});
it("서명 만들기", () => {
const keyPair = ec.keyFromPrivate(priveKey);
// 임시 트랜잭션 내용
const hash = SHA256("transaction data").toString();
// sign 서명 생성
signature = keyPair.sign(hash, "hex");
console.log("서명:", signature);
});
// r 서명,s서명
// BN=BigNumber 무척 큰 number타입
// negative: 0 =>양수
// words:r 서명이나 s서명의 값을 32비트 정수 배열로 표시한 값
// length:배열의 길이
// 서명: Signature {
// r: BN {
// negative: 0,
// words: [
// 43348871, 20617926,
// 52805612, 40572145,
// 42203394, 50772247,
// 60751715, 62974710,
// 58451527, 423078
// ],
// length: 10,
// red: null
// },
// s: BN {
// negative: 0,
// words: [
// 8906980, 2268512, 54377430, 40918456,
// 64234097, 46754793, 2550136, 7621258,
// 65804558, 1847326, 0, 0,
// 0, 0, 0, 0,
// 0, 0, 0, 0,
// 0, 0, 0, 0,
// 0, 0, 0, 0,
// 0, 0
// ],
// length: 10,
// red: null
// },
// recoveryParam: 1
// }
it("검증하기", () => {
const hash = SHA256("transaction data").toString();
const verify = ec.verify(
hash,
signature,
ec.keyFromPublic(publicKey, "hex")
);
console.log("검증됨?", verify);
});
// 지갑 주소 생성
it("지갑 주소", () => {
// 계정을 만드는 방법은 만든 공개키의 값에서 26개의 문자열을 앞에서 잘라서
// 40자리 만큼을 남겨서 주소로 사용한다.
// 불필요한 부분 제거하고 앞에 0x
// 가독성 때문에.... 주소의 앞에는 0x붙이는 것이 일반적 (16진수)
const address = publicKey.slice(26).toString();
console.log("지갑주소:", `0x${address}`);
});
});
const verify = ec.verify(
hash,
signature,
ec.keyFromPublic(publicKey, "hex")
);
import { randomBytes } from "crypto";
import elliptic from "elliptic";
import { SHA256 } from "crypto-js";
import fs from "fs";
import path from "path";
// 지갑 클래스 만들고 페이지에서 지갑 한 번 확인 해보기
// elliptic인스턴스 생성
const ec = new elliptic.ec("secp256k1");
// 지갑정보 저장 기본 경로
const dir = path.join(__dirname, "..", "..", "data");
// 지갑 클래스 정의
class Wallet {
public account: string;
public privateKey: string;
public publicKey: string;
public balance: number;
constructor(privateKey: string = "") {
// 생성 단계에서 개인키 값이 없으면 만들어 넣자
this.privateKey = privateKey || this.getPrivateKey();
this.publicKey = this.getPublicKey();
this.account = this.getAccount();
this.balance = 0;
if (privateKey === "") {
Wallet.createWallet(this);
}
}
public getPrivateKey(): string {
return randomBytes(32).toString("hex");
}
public getPublicKey(): string {
// 개인키로 공개키를 만들자
const keyPair = ec.keyFromPrivate(this.privateKey);
return keyPair.getPublic().encode("hex", false);
}
public getAccount(): string {
return `0x${this.publicKey.slice(26).toString()}`;
}
static createWallet(mywallet: Wallet) {
// fs 모듈로 파일 생성
// 지갑을 생성하면 주소를 저장할 것
const filename = path.join(dir, mywallet.account);
const fileContent = mywallet.privateKey;
fs.writeFileSync(filename, fileContent);
}
// data폴더안에 있는 지갑주소를 찾아서 반환
static getWalletPrivateKey(account: string): string {
const filename = path.join(dir, account);
const fileContent = fs.readFileSync(filename);
return fileContent.toString();
}
static getWalletList(): string[] {
// readFileSync 폴더를 읽어서 안에있는 파일 내용을 문자열로 가져온다.
const files: string[] = fs.readdirSync(dir);
return files;
}
}
export default Wallet;
// 지갑 서버
import express from "express";
import Wallet from "@coreWallet/wallet/index";
import path from "path";
import fs from "fs";
const app = express();
app.use(express.json());
app.use(
express.urlencoded({
extended: false,
})
);
// 지갑 페이지 접속
app.get("/", (req, res) => {
const page = fs.readFileSync(
path.join(__dirname, "..", "wallet", "view.html"),
"utf-8"
);
res.send(page);
});
// 지갑을 생성 요청
app.post("/newWallet", (req, res) => {
res.json(new Wallet());
});
// 지갑들 불러오기
app.post("/walletList", (req, res) => {
const list = Wallet.getWalletList();
res.json(list);
});
// 해당 지갑 주소로 지갑 찾기
app.post("/walletSelect", (req, res) => {
const { account } = req.body;
console.log(account);
const privateKey = Wallet.getWalletPrivateKey(account);
res.json(new Wallet(privateKey));
});
app.listen(4000, () => {
console.log("gogo");
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.5.0/axios.min.js"
integrity="sha512-aoTNnqZcT8B4AmeCFmiSnDlc4Nj/KPaZyB5G7JnOnUEkdNpCZs1LCankiYi01sLTyWy+m2P+W4XM+BuQ3Q4/Dg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
</head>
<body>
<h1>지갑 듀토리얼</h1>
<button id="walletBtn">지갑 생성</button>
<ul id="walletList">
<li>비트코인 지갑</li>
<li>account: <span id="account"></span></li>
<li>privateKey: <span id="privateKey"></span></li>
<li>publicKey: <span id="publicKey"></span></li>
<li>balance: <span id="balance"></span></li>
</ul>
<h1>생성된 지갑 목록</h1>
<button id="walletListBtn">지갑 목록 조회</button>
<div>
<ul id="walletListData">
지갑 조회 눌렁
</ul>
</div>
</body>
<script>
const render = (wallet) => {
account.innerHTML = wallet.account;
privateKey.innerHTML = wallet.privateKey;
publicKey.innerHTML = wallet.publicKey;
balance.innerHTML = wallet.balance;
};
walletBtn.onclick = async () => {
const { data: resp } = await axios.post("/newWallet");
console.log(resp);
render(resp);
};
const getView = async (account) => {
console.log(account);
const { data: resp } = await axios.post("/walletSelect", { account });
render(resp);
};
walletListBtn.onclick = async () => {
const { data: resp } = await axios.post("/walletList");
const list = resp.map((account) => {
return `<litoken interpolation">${account}')"">${account} </li>`;
});
walletListData.innerHTML = list;
};
</script>
</html>
<li">${account} </li>
이부분에서 onclick 부분을 double quotes로 감싸야한다는 것이다. account는 파라미터로 전달될 때 string으로 전달되어야 하므로 '${account}' => single quote로 감싸야 한다는 점이다.메타마스크에서 사용되는 월렛이 이런 원리로 생성되고 인증된다는 점을 처음 알았다. 특히 비대칭키는 기사공부를 할 때 많이 봐왔지만 실제로 사용한적은 처음이다. 비록 라이브러리이지만, 개인키로 키쌍과 공개키를 생성하고 이런 공개키로 메시지 데이터를 암호화 시키고 개인키를 자신만이 복호화가 가능하고, 다른 사람들은 공개키와 서명으로 유효한지를 판별하는 과정은 매우 흥미로웠다.