Payon Moduel Flow
class PayonReader @Inject constructor(
private val nfcAdapter: PayonNfcAdapter,
private val payonApiService: RetrofitPayonApi
) : ReaderStrategy {
companion object {
const val clientType = "****"
const val osVer = "**
const val osName = ""****"
const val appId = ""*********"
const val validKey = ""*************"
const val envCode = ""****"
const val deviceId = ""*************"
}
override suspend fun readCard(tag: Tag): Flow<CardResult> = flow {
emit(CardResult.Loading(true))
val result = runCatching {
val auth = safeAuthRequest()
TagCryptoConverter().extractCryptoParamsFromKeySeed(auth.key)
val cid = nfcAdapter.getMifareCid(tag) ?: throw CardException.CidError("CID is null")
val sector0KeyRequestData = TagCryptoConverter().encryptMifareCid(cid)
val sector0Key = sector0KeyRequest(auth.trId ?: "", sector0KeyRequestData)
val certifyCode = nfcAdapter.getCertifyCode(tag, sector0Key) ?: throw CardException.CertifyCodeError("CertifyCode is null")
val sector12KeyRequestData = TagCryptoConverter().encryptCertifyCode(sector0KeyRequestData.payonCard.chipSerialNumber, certifyCode)
val sector12Key = sector12KeyRequest(auth.trId ?: "", sector12KeyRequestData)
val encCardData = nfcAdapter.getEncCardData(tag, sector12Key) ?: throw CardException.CidError("Enc Card Data is null")
CardResult.Success(CardData(encCardData,byteArrayOf(),byteArrayOf()))
}.getOrElse { exception ->
handleCardException(exception)
}
emit(CardResult.Loading(false))
emit(result)
}
private suspend fun safeAuthRequest(): PayonAuthResponse {
val response = payonApiService.auth(
clientType = clientType,
osVer = osVer,
osName = osName,
appId = appId,
validKey = validKey,
envCode = envCode,
deviceId = deviceId
)
if (!response.isSuccessful) {
val error = parseError(response)
throw CardException.NetworkError(
code = error?.code ?: response.code().toString(),
message = error?.message ?: "Unknown server error"
)
}
return response.body() ?: throw CardException.AuthError("Auth body null")
}
private suspend fun sector0KeyRequest(transactionId: String, sector0KeyRequestData: SectorKeyDataRequest): String {
val response = payonApiService.getSector0Key(
clientType = clientType,
osVer = osVer,
osName = osName,
appId = appId,
validKey = validKey,
envCode = envCode,
deviceId = deviceId,
trId = transactionId,
body = sector0KeyRequestData
)
if (!response.isSuccessful) {
val error = parseError(response)
throw CardException.NetworkError(
code = error?.code ?: response.code().toString(),
message = error?.message ?: "Unknown server error"
)
}
return TagCryptoConverter().decryptSector0Key(response.body()?.payonCard?.sector0KeyA ?: "")
}
private suspend fun sector12KeyRequest(transactionId: String, sector12KeyRequestData: SectorKeyDataRequest): String {
val response = payonApiService.getSector12Key(
clientType = clientType,
osVer = osVer,
osName = osName,
appId = appId,
validKey = validKey,
envCode = envCode,
deviceId = deviceId,
trId = transactionId,
body = sector12KeyRequestData
)
if (!response.isSuccessful) {
val error = parseError(response)
throw CardException.NetworkError(
code = error?.code ?: response.code().toString(),
message = error?.message ?: "Unknown server error"
)
}
return TagCryptoConverter().decryptSector12Key(response.body()?.payonCard?.sector12KeyA ?: "")
}
}
class PayonNfcAdapter @Inject constructor(
private val context: Context
) {
private val nfcAdapter: NfcAdapter = NfcAdapter.getDefaultAdapter(context)
fun getMifareCid(tag: Tag): ByteArray? {
val mifare = MifareClassic.get(tag)
return try {
if(!mifare.isConnected){
mifare.connect()
}
var id = mifare.tag.id
if(id.isEmpty()){
id = mifare.readBlock(0)
}
id
} catch (e: Exception) {
Timber.d("getMifareCid Exception : $e")
null
} finally {
try {
mifare.close()
} catch (e: Exception) {
Timber.d("mifare close Exception : $e")
}
}
}
fun getCertifyCode(tag: Tag, sector0Key: String): ByteArray?{
val mifare = MifareClassic.get(tag)
return try {
if(!mifare.isConnected){
mifare.connect()
}
val authenticateSector0 = mifare.authenticateSectorWithKeyA(0, Util().hexToByte(sector0Key))
Timber.d("authenticateSector0 : $authenticateSector0")
var certifyCode: ByteArray? = null
if(authenticateSector0){
certifyCode = mifare.readBlock(2)
Timber.d("certifyCode : $certifyCode")
}
certifyCode
} catch (e: Exception) {
Timber.d("getCertifyCode Exception : $e")
null
} finally {
try {
mifare.close()
} catch (e: Exception) {
Timber.d("mifare close Exception : $e")
}
}
}
fun getEncCardData(tag: Tag, sector12Key: String): ByteArray?{
val mifare = MifareClassic.get(tag)
return try {
if(!mifare.isConnected){
mifare.connect()
}
val authenticateSector12 = mifare.authenticateSectorWithKeyA(12, Util().hexToByte(sector12Key))
Timber.d("authenticateSector12 : $authenticateSector12")
var encCardData: ByteArray? = null
if(authenticateSector12){
encCardData = mifare.readBlock( 48)
Timber.d("encCardData : $encCardData")
}
encCardData
} catch (e: Exception) {
Timber.d("getEncCardData Exception : $e")
null
} finally {
try {
mifare.close()
} catch (e: Exception) {
Timber.d("mifare close Exception : $e")
}
}
}
}
class TagCryptoConverter {
companion object {
private var keySeed: ByteArray?= null
private var passphrase: String?= null
private var salt: ByteArray?= null
private var iv: ByteArray?= null
}
fun extractCryptoParamsFromKeySeed(key: String?){
try {
keySeed = Base64.decode(key, Base64.NO_WRAP)
keySeed?.let { seed ->
passphrase = CipherUtil.getPassphraseFromKeySeed(seed)
salt = CipherUtil.getSaltFromKeySeed(seed)
iv = CipherUtil.getIvFromKeySeed(seed)
}
} catch (e: Exception){
CardException.TagCryptoError(
message = "extractCryptoParamsFromKeySeed Error : ${e.message}"
)
}
}
fun encryptMifareCid(
cid: ByteArray
): SectorKeyDataRequest {
try {
val encryptCid = CipherUtil.encrypt(Util().bytesToHex(cid).toByteArray(), passphrase, salt, iv)
val base64Encoder = Base64.encodeToString(encryptCid, Base64.NO_WRAP)
Timber.d("encryptMifareCid : $base64Encoder")
return SectorKeyDataRequest(SectorPayonData(base64Encoder,null),"K1")
} catch (e: Exception){
throw CardException.TagCryptoError(
message = "encryptMifareCid Error : ${e.message}"
)
}
}
fun decryptSector0Key(
encryptSector0Key: String
): String {
try {
val base64Sector0Key = Base64.decode(encryptSector0Key, Base64.NO_WRAP)
val sector0Key = String(CipherUtil.decrypt(base64Sector0Key, passphrase!!, salt, iv), StandardCharsets.UTF_8)
return sector0Key
} catch (e: Exception){
throw CardException.TagCryptoError(
message = "decryptSector0Key Error : ${e.message}"
)
}
}
fun encryptCertifyCode(
base64Cid: String,
certifyCode: ByteArray
): SectorKeyDataRequest{
try {
val encryptCertifyCode = CipherUtil.encrypt(Util().bytesToHex(certifyCode).toByteArray(), passphrase!!, salt, iv)
val base64Encoder = Base64.encodeToString(encryptCertifyCode, Base64.NO_WRAP)
return SectorKeyDataRequest(SectorPayonData(base64Cid,base64Encoder),"K1")
} catch (e: Exception){
throw CardException.TagCryptoError(
message = "encryptCertifyCode Error : ${e.message}"
)
}
}
fun decryptSector12Key(
encryptSector12Key: String
): String {
try {
val base64Sector12Key = Base64.decode(encryptSector12Key, Base64.NO_WRAP)
val sector12Key = String(CipherUtil.decrypt(base64Sector12Key, passphrase!!, salt, iv), StandardCharsets.UTF_8)
return sector12Key
} catch (e: Exception){
throw CardException.TagCryptoError(
message = "decryptSector0Key Error : ${e.message}"
)
}
}
}
fun handleCardException(exception: Throwable): CardResult.Error {
return when (exception) {
is CardException.NetworkError ->
CardResult.Error(
code = exception.code,
message = exception.message ?: "Network error",
throwable = exception
)
is CardException.AuthError ->
CardResult.Error(
code = "AUTH_ERROR",
message = exception.message ?: "Auth error",
throwable = exception
)
is CardException.CidError ->
CardResult.Error(
code = "CID_ERROR",
message = exception.message ?: "CID read fail",
throwable = exception
)
is CardException.CertifyCodeError ->
CardResult.Error(
code = "CERTIFY_CODE_ERROR",
message = exception.message ?: "Certify Code read fail",
throwable = exception
)
is CardException.EncCardDataError ->
CardResult.Error(
code = "ENC_CARD_DATA_ERROR",
message = exception.message ?: "Enc Card Data read fail",
throwable = exception
)
else ->
CardResult.Error(
code = "UNKNOWN",
message = exception.message ?: "Unknown error",
throwable = exception
)
}
}
sealed class CardException(message: String) : Exception(message) {
class NetworkError(val code: String, message: String) : CardException(message)
class AuthError(message: String) : CardException(message)
class CidError(message: String) : CardException(message)
class CertifyCodeError(message: String) : CardException(message)
class EncCardDataError(message: String) : CardException(message)
class TagCryptoError(message: String) : CardException(message)
class UnknownError(message: String) : CardException(message)
}
- 코드를 작성하면서 생각한 것 → 확장성을 생각한 공통 cardData, errorData 정의
- 중복코드 제거 목표