(1) public key를 숨겨라
※ 서버검증할거라면 클라이언트(앱)에서 public_key는 필요가 없다. => 자세한내용보기
/* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY
* (that you got from the Google Play developer console). This is not your
* developer public key, it's the *app-specific* public key.
*
* Instead of just storing the entire literal string here embedded in the
* program, construct the key at runtime from pieces or
* use bit manipulation (for example, XOR with some other string) to hide
* the actual key. The key itself is not secret information, but we don't
* want to make it easy for an attacker to replace the public key with one
* of their own and then fake messages from the server.
*/
String base64EncodedPublicKey = "CONSTRUCT_YOUR_KEY_AND_PLACE_IT_HERE";
인앱기능을 구현하기위해 샘플 코드들을 가져다 놓고서 열어보면 이런 부분이 있다.
위의 /* 코멘트 */ 부분은 눈에 잘 들어오지 않고 (영어라서!) "Your key PLACE_IT_HERE"라는 문구가 보인다. 얼른 복사해서 붙여넣고 넘어가려는게 일단 문제다.
그냥 붙여넣지 말자. Construct를 해서 넣어야 한다. 한번쯤은 꼬아 놓자.
String piece1 = "SDFGJKGB4UIH234WE/FRT23RS#trashdata#DF/3DFUISDFVWE";
String piece2 = "SDFGJKGB4UIHUI#trashdata#SDFVWE";
String piece3 = "BDY#trashdata#ASGBDNAWGRET24IYE23das4saGBENWKD";
String piece4 = "#trashdata#432423SDF23R/+SDDS";
mHelper = new IabHelper(this, piece1.substring(...) + piece2.substring(...) + piece3.substring(...) + piece4.substring(...));
이런식으로만 해 두어도 난이도가 올라간다. 크래커입장에서는 이 키를 자기자신의 키로 변경하고자 시도할텐데, 일반 문자열로 들어가 있으면 식은죽 먹기일테지.권장하는 방법은 실시간으로 서버로부터 이 키를 암호화된채로 받아서 복호화해서 IabHelper를 초기화하는 것.
(2) 결제과정에서 구글지갑을 꼭 체크하자.
유저가 결제한 직후에, launchPurchaseFlow -> QueryInventory -> Consume -> onFinishedConsume 의 절차를 거치도록 하자.
*) 결제후에, 인벤토리에 그 아이템이 있는지 검사하고, 컨슘(사용)한다음에 최종적으로 OK가 나왔을때에 서버측에 전달하여 유료아이템 구매를 처리하는 식.
구글에서 제공해주는 샘플소스를 잘 살펴보자.
(3) 구매완료후 영수증을 확인한다. (Clinet Side)
※ 영수증을 서버에서 검증한다면 굳이 검증할 필요는 없다.
public void onConsumeFinished(Purchase purchase, IabResult result) {
if (result.isSuccess()) {
if(purchase != null){
// provision the in-app purchase to the user
verifyBilling(purchase.getSignature(), purchase.getOriginalJson());
} else {
LogToServer("onConsumeFinished:purchase=null");
}
} else {
LogToServer("onConsumeFinished:consume=failed");
}
}
your_publickey, json_data, signature로 검증을 할 수 있다. (Security.Java에 잘 구현되어 있다)
(4) 서버측에서 검증한다 (Server Side)
영수증데이터를 서버로 보내 서버에서 검증하는 방법이다.
인터넷을 뒤져서 c++로 구현된 서버측 검증소스를 하나 찾아냈는데, 잘 동작하지 않는다. 게다가 이렇게 뭐가뭔지 모를 소스코드는 기겁하게 만든다.
int InappBillingVerify(const char* data, const char* signature, const char* pub_key_id) { std::shared_ptr<EVP_MD_CTX> mdctx = std::shared_ptr<EVP_MD_CTX>(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); const EVP_MD* md = EVP_get_digestbyname("SHA1"); EVP_VerifyInit_ex(mdctx.get(), md, NULL); EVP_VerifyUpdate(mdctx.get(), (void*)data, strlen(data)); std::shared_ptr<BIO> b64 = std::shared_ptr<BIO>(BIO_new(BIO_f_base64()), BIO_free); BIO_set_flags(b64.get(),BIO_FLAGS_BASE64_NO_NL); std::shared_ptr<BIO> bPubKey = std::shared_ptr<BIO>(BIO_new(BIO_s_mem()), BIO_free); BIO_puts(bPubKey.get(),pub_key_id); BIO_push(b64.get(), bPubKey.get()); std::shared_ptr<EVP_PKEY> pubkey = std::shared_ptr<EVP_PKEY>(d2i_PUBKEY_bio(b64.get(), NULL), EVP_PKEY_free); std::string decoded_signature = Base64Decode(std::string(signature)); return EVP_VerifyFinal(mdctx.get(), (unsigned char*)decoded_signature.c_str(), decoded_signature.length(), pubkey.get()); }
출처 : http://kukuta.tistory.com/166
이 분의 소스를 가지고 구현해보았지만 에러가 나면서 실패. 소스코드를 보고 있으면 머리가 지끈지끈하다.
20년을 프로그래밍을 해왔는데, 이 소스는 대체 뭐란 말이냐.... 이유없이 Segmentation Fault 가 나오거나 에러코드 -1이 떨어지는데, 왜 안되는지 이유조차 알 수가 없고, 직접좀 만들어볼까 하니 외계인이 만들어 놓은것 같은 openssl 함수들과 구조체들을 보면 게보린 한통은 필요해 보인다.
결국 포기 T.T
불행한 것은 전 세계를 다 뒤졌음에도, 아직 잘 동작하는 c++소스를 못 찾았았다는 것. 결국 임시방편으로 내가 쓴 방법은 이렇다.
1) Security.Java의 verify를 호출하여 검증하는 Java프로그램을 만든후에, 실행가능한 jar로 만든다.
2) 게임서버에서 java를 호출하여 실행시킨후에 출력값을 얻어온다. (리눅스 pipe를 이용한다)
*. 이 과정에서 jar가 정상 실행되지 않거나 ClassNotFound등의 오류에 시달려야 했는데, 패키지이름을 부여하지 않아서라든지 jdk 버전이 이클립스개발용과 서버용이 달라서 생기는 문제들.
(4-1) 구글 개발자 API를 이용.
구글은 "판매자의 지갑"을 들여다 볼 수 있는 API를 제공한다. 하지만 구현이 만만치 않다. 게다가 c++ 버전의 샘플이나 라이브러리는 제공하지도 않는다.느리고, 하루 20만회 호출로 제한되어 있으며, 가끔 응답없음이 나오기도 한다그래서 일단 패쓰.
(*) 프리덤!
구글 인앱결제를 우회하게 해주는 프리덤이라는 앱이 있는데, (프리덤, freedom, ㅍㄹㄷ 으로 검색) 가짜 카드로 무한 결제가 가능한 무시무시한 앱이다.
4단계의 서버검증에서 걸러낼 수 있다. 그러니까, 인앱결제를 넣을거라면 서버검증은 필수라는 것.