Android에서 AES 암호화하기
private fun encrypt(jwt: String, name: String): String {
val data = JSONObject()
.put("jwt", jwt)
.put("name", name)
.toString()
try {
val key = EncryptUtils.generateSHA256("super-secret-key")
val iv = ByteArray(16) { 0 }
val skeySpec = SecretKeySpec(key, "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, IvParameterSpec(iv))
val clearText = data.toByteArray()
val data = ByteArray(((clearText.size / 16) + 1) * 16)
System.arraycopy(clearText, 0, data, 0, clearText.size)
val encrypted = cipher.doFinal(data)
return Base64.encodeToString(encrypted, Base64.NO_WRAP)
} catch (ex: Exception) {
DUtils.notReached(ex)
throw ex;
}
}
@WorkerThread
@Throws(Exception::class)
fun qrcodeAccept(code: String) {
val data = encrypt(getJwtForDesktop(), getDbName())
val parameter = JSONObject()
.put("encrypted", true)
.put("data", data)
request("/v1/qrcode-accept/$code", parameter, true)
}
Cpp 클라이언트에서 복호화하기
간단히 AES를 풀수있는 라이브러리 SergeyBel/AES: C++ AES implementation (github.com) 를 사용.
간단히 json을 다룰수 있는 라이브러리 nlohmann/json: JSON for Modern C++ (github.com) 사용
int CDlgQrcode::handleTokenData(std::string& data) {
dfx::ByteArray encrypted;
if (!dfx::Base64_DecodeA(data.c_str(), encrypted))
return 1;
const char* key = "super-secret-key";
BYTE aesKey[32];
dsMakeSHA256((BYTE*)key, strlen(key), aesKey);
BYTE ivKey[16] = { 0 };
ZeroMemory(ivKey, 16);
AES aes(256);
unsigned char* result = aes.DecryptCBC(encrypted.GetBuffer(), encrypted.GetSize(), aesKey, ivKey);
std::string jsonString = (char*)result;
delete[] result;
try {
json jsonObject = json::parse(jsonString.c_str());
std::string jwt = jsonObject.at("jwt").get<std::string>();
std::string name = jsonObject.at("name").get<std::string>();
CString dbName = dfx::Utf8toUnicode(name.data());
CDialog::OnOK();
}
catch (const std::exception& e) {
::AfxMessageBox(L"json parsing failed:\n" + dfx::Utf8toUnicode(jsonString.c_str()));
}
return 0;
}
보안 강화를 위해서 JWT 토큰 만들기
h5p9sl/hmac_sha256: Minimal HMAC-SHA256 implementation in C / C++ (github.com) 를 코드를 이용한다. 간단히 소스파일 2개만 추가해서 구현..
std::string base64url_encode(BYTE* data, int size) {
CAtlStringA base64 = dfx::Base64_EncodeA(data, size);
base64.Replace("=", "");
base64.Replace("+", "-");
base64.Replace("/", "_");
return base64;
}
std::string getJwtAuth(std::string code) {
const std::string header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
CTime ct = CTime::GetCurrentTime();
__time64_t currentTimeSeconds = ct.GetTime();
__time64_t exp = currentTimeSeconds + 60; // 60 seconds 유효
std::string payload = string_format("{\"exp\":%ld, \"code\":\"", exp);
payload += code;
payload += "\"}";
const std::string header_encoded = base64url_encode((BYTE*)header.data(), header.size());
const std::string payload_encoded = base64url_encode((BYTE*)payload.data(), payload.size());
const std::string data = header_encoded + "." + payload_encoded;
const std::string key = "super-secret-key";
BYTE hmac[32];
hmac_sha256(key.data(), key.size(), data.data(), data.size(), hmac, 32);
const std::string signiture = base64url_encode(hmac, 32);
return header_encoded + "." + payload_encoded + "." + signiture;
}
deno/ oak에서 jwt-auth 헤더 확인
exp로 유효시간 확인, jwt의 payload안의 code와 일치하는지 확인
/**
* verifyJwtAuth
*/
import { hmac as createHmac} from "https://deno.land/x/crypto@v0.10.0/hmac.ts"
import {encode as base64urlEncode, decode as base64urlDecode} from "https://deno.land/std@0.103.0/encoding/base64url.ts"
const PRIVATE_SECRET = new TextEncoder().encode("super-secret-key")
export function verifyJwtAuth(token: string, code: string) {
if (token == null) {
throwJwtError("empty token")
}
const [header, payload, signiture] = token.split('.')
const value = new TextEncoder().encode(header + "." + payload)
const hmacValue = createHmac('sha256', PRIVATE_SECRET, value)
const signiture2 = base64urlEncode(hmacValue)
if (signiture !== signiture2) {
throwJwtError("invalid signiture")
}
const payloadJson: string = new TextDecoder().decode(base64urlDecode(payload))
const payloadObj = JSON.parse(payloadJson)
if (code !== payloadObj.code) {
throwJwtError("invalid code")
}
const exp = payloadObj.exp || null
if (typeof exp != "number") {
throwJwtError("invalid exp")
}
if (exp <= Math.floor(Date.now()/1000)) {
throw new ErrorCode("jwt auth error: expired token", kErrorJwtExpire)
}
}