KNETApiSigner umożliwia generowanie podpisów kryptograficznych
dla requestów API. Obsługuje popularne schematy używane przez AWS, Stripe, i inne serwisy.
import rip.nerd.kitsunenet.security.KNETApiSigner
// Utwórz signer z secret key
val signer = KNETApiSigner.hmacSha256("your-secret-key-here")
// Podpisz request
val request = KNETRequest.post(
url = "https://api.example.com/orders",
data = mapOf("product_id" to 123, "quantity" to 2)
)
val signedRequest = signer.sign(request)
// signedRequest zawiera teraz nagłówki:
// X-Signature: a1b2c3d4e5f6...
// X-Timestamp: 1609459200
// X-Nonce: abc123def456
// Wykonaj podpisany request
val response = client.request(signedRequest)
| Algorytm | Metoda | Użycie |
|---|---|---|
| HMAC-SHA256 | hmacSha256() |
Najpopularniejszy, Stripe, większość API |
| HMAC-SHA512 | hmacSha512() |
Wyższe bezpieczeństwo |
| AWS Signature V4 | awsStyle() |
Amazon Web Services |
| Stripe Webhook | stripeStyle() |
Weryfikacja webhooków Stripe |
| Simple Hash | simpleHash() |
Proste API key + timestamp |
| Custom | custom() |
Dowolna logika |
hmacSha256()Tworzy signer HMAC-SHA256 - najpopularniejszy standard.
fun hmacSha256(
secret: String,
headerName: String = "X-Signature"
): KNETApiSigner
val signer = KNETApiSigner.hmacSha256("my-api-secret")
val request = KNETRequest.post(
"https://api.example.com/payments",
mapOf("amount" to 1000, "currency" to "PLN")
)
val signedRequest = signer.sign(request)
// Nagłówki po podpisaniu:
println(signedRequest.headers)
// {
// "X-Signature": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
// "X-Timestamp": "1609459200",
// "X-Nonce": "a1b2c3d4e5f6g7h8"
// }
hmacSha512()Tworzy signer HMAC-SHA512 - dłuższy hash, wyższe bezpieczeństwo.
fun hmacSha512(
secret: String,
headerName: String = "X-Signature"
): KNETApiSigner
val signer = KNETApiSigner.hmacSha512("my-secure-secret")
val signedRequest = signer.sign(request)
// Generuje 128-znakowy hex signature
awsStyle()Uproszczona wersja AWS Signature Version 4.
fun awsStyle(
accessKey: String,
secretKey: String,
region: String = "us-east-1",
service: String = "execute-api"
): KNETApiSigner
val signer = KNETApiSigner.awsStyle(
accessKey = "AKIAIOSFODNN7EXAMPLE",
secretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
region = "eu-central-1",
service = "execute-api"
)
val request = KNETRequest.get("https://api.aws-example.com/items")
val signedRequest = signer.sign(request)
// Nagłówki:
// Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20210101/eu-central-1/execute-api/aws4_request, Signature=...
// X-Amz-Date: 20210101T120000Z
To jest uproszczona implementacja AWS Sig V4. Dla pełnej kompatybilności z AWS SDK użyj oficjalnego AWS SDK dla Androida.
stripeStyle()Generowanie i weryfikacja podpisów w stylu Stripe webhook.
fun stripeStyle(webhookSecret: String): KNETApiSigner
// W Twojej obsłudze webhook:
val signer = KNETApiSigner.stripeStyle("whsec_your_webhook_secret")
// Payload z body requestu webhook
val payload = """{"type":"payment_intent.succeeded","data":{"amount":1000}}"""
// Signature z nagłówka Stripe-Signature
val signatureHeader = "t=1609459200,v1=abc123..."
// Timestamp z nagłówka (sekundy)
val timestamp = 1609459200L
// Weryfikacja
val isValid = signer.verify(payload, signatureHeader, timestamp)
if (isValid) {
// Webhook jest autentyczny
processWebhook(payload)
} else {
// Odrzuć - możliwy atak
Log.w("Webhook", "Invalid signature!")
return HttpResponse(401, "Invalid signature")
}
simpleHash()Prosty hash: apiKey + timestamp + secret.
fun simpleHash(apiKey: String, secret: String): KNETApiSigner
val signer = KNETApiSigner.simpleHash(
apiKey = "my-api-key",
secret = "my-secret"
)
val signedRequest = signer.sign(request)
// Generuje: SHA256(apiKey + timestamp + secret)
custom()Całkowicie niestandardowa logika podpisywania.
fun custom(
builder: (SignatureData) -> String
): KNETApiSigner
val signer = KNETApiSigner.custom { data ->
// data zawiera: method, url, headers, body, timestamp, nonce
// Twoja własna logika
val payload = buildString {
append(data.method)
append("|")
append(data.url)
append("|")
append(data.timestamp)
append("|")
append(data.body ?: "")
}
// Użyj dowolnego algorytmu
MessageDigest.getInstance("SHA-256")
.digest(payload.toByteArray())
.joinToString("") { "%02x".format(it) }
}
val signedRequest = signer.sign(request)
sign()Podpisuje request dodając nagłówki.
fun sign(request: KNETRequest): KNETRequest
Dodaje następujące nagłówki:
X-Signature (lub inny skonfigurowany)X-Timestamp (Unix timestamp)X-Nonce (unikalny identyfikator)generateSignature()Generuje tylko podpis (bez modyfikacji requestu).
fun generateSignature(request: KNETRequest): String
val signature = signer.generateSignature(request)
println("Signature: $signature")
// Możesz użyć tej wartości ręcznie
verify()Weryfikuje podpis (do webhooków).
fun verify(
payload: String,
signature: String,
timestamp: Long? = null
): Boolean
val isValid = signer.verify(
payload = requestBody,
signature = receivedSignature,
timestamp = receivedTimestamp
)
if (!isValid) {
throw SecurityException("Invalid signature")
}
Pełna kontrola nad konfiguracją signera:
val signer = KNETApiSigner.builder()
.algorithm("HmacSHA256") // Algorytm HMAC
.secret("my-secret-key") // Secret key
.headerName("X-API-Signature") // Nazwa nagłówka z podpisem
.includeTimestamp(true) // Czy dodać timestamp
.timestampHeaderName("X-Request-Time") // Nazwa nagłówka timestamp
.customBuilder { data -> // Opcjonalnie: custom logika
// własna implementacja
}
.build()
| Metoda | Typ | Domyślnie | Opis |
|---|---|---|---|
algorithm() |
String | "HmacSHA256" | Algorytm HMAC |
secret() |
String | "" | Secret key |
headerName() |
String | "X-Signature" | Nagłówek z podpisem |
includeTimestamp() |
Boolean | true | Czy dodać timestamp |
timestampHeaderName() |
String | "X-Timestamp" | Nagłówek timestamp |
Dane dostępne w custom builder:
data class SignatureData(
val method: String, // GET, POST, etc.
val url: String, // Pełny URL
val headers: Map<String, String>, // Nagłówki requestu
val body: String?, // Body jako string
val timestamp: Long, // Unix timestamp (sekundy)
val nonce: String? // Unikalny identyfikator
)
class SecureApiClient(
private val apiKey: String,
private val apiSecret: String
) {
private val client = KNETClient()
private val signer = KNETApiSigner.hmacSha256(apiSecret)
suspend fun createOrder(order: Order): OrderResult {
val request = KNETRequest(
url = "https://api.secure.com/orders",
method = "POST",
headers = mapOf(
"X-API-Key" to apiKey,
"Content-Type" to "application/json"
),
data = order.toMap()
)
// Podpisz request
val signedRequest = signer.sign(request)
// Wykonaj
val response = client.request(signedRequest)
return response.json<OrderResult>()
}
}
class SigningInterceptor(
private val signer: KNETApiSigner
) : KNETInterceptor {
override suspend fun intercept(
request: KNETRequest,
chain: suspend (KNETRequest) -> KNETResponse
): KNETResponse {
// Podpisz każdy request automatycznie
val signedRequest = signer.sign(request)
return chain(signedRequest)
}
}
// Użycie
val client = KNETClient.builder()
.addInterceptor(SigningInterceptor(
KNETApiSigner.hmacSha256("secret")
))
.build()
// Teraz każdy request będzie automatycznie podpisany
val response = client.get("https://api.example.com/data")
// W endpoint obsługującym webhook (np. z Ktor/Spring)
@PostMapping("/webhook/stripe")
fun handleStripeWebhook(
@RequestBody payload: String,
@RequestHeader("Stripe-Signature") signature: String
): ResponseEntity<*> {
val signer = KNETApiSigner.stripeStyle(
System.getenv("STRIPE_WEBHOOK_SECRET")
)
// Parsuj timestamp z nagłówka
val timestamp = signature
.split(",")
.find { it.startsWith("t=") }
?.substringAfter("t=")
?.toLongOrNull()
// Weryfikuj podpis
if (!signer.verify(payload, signature, timestamp)) {
return ResponseEntity.status(401).body("Invalid signature")
}
// Sprawdź czy nie za stary (5 min tolerance)
val now = System.currentTimeMillis() / 1000
if (timestamp != null && (now - timestamp) > 300) {
return ResponseEntity.status(401).body("Timestamp too old")
}
// Przetwórz webhook
val event = parseStripeEvent(payload)
processEvent(event)
return ResponseEntity.ok().build()
}
// Niektóre API wymagają określonych nagłówków
val signer = KNETApiSigner.builder()
.algorithm("HmacSHA256")
.secret("my-secret")
.headerName("Authorization") // Zamiast X-Signature
.timestampHeaderName("Date") // Standard HTTP
.customBuilder { data ->
// Formatuj jako Bearer token z signature
val signature = hmacSha256(
"${data.method}|${data.url}|${data.timestamp}",
"my-secret"
)
"HMAC-SHA256 $signature"
}
.build()
// ❌ ŹLE - hardcoded secret
val signer = KNETApiSigner.hmacSha256("my-secret-key")
// ✅ DOBRZE - z BuildConfig
val signer = KNETApiSigner.hmacSha256(BuildConfig.API_SECRET)
// ✅ DOBRZE - z EncryptedSharedPreferences
val secret = encryptedPrefs.getString("api_secret", null)
?: throw SecurityException("API secret not configured")
val signer = KNETApiSigner.hmacSha256(secret)
// ✅ DOBRZE - z Android Keystore
val secret = keystore.getSecretKey("api_secret")
val signer = KNETApiSigner.hmacSha256(secret)