diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt
index 310cf7dc2126ffed32f375229897b36521c539d8..e05c6a6da773cb91a7df92ed8f0e298250054efd 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt
@@ -19,7 +19,10 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona
 
     override fun extract(rawString: String): CoronaTestQRCode.RapidAntigen {
         Timber.v("extract(rawString=%s)", rawString)
-        val payload = extractData(rawString)
+        val payload = CleanPayload(extractData(rawString))
+
+        payload.requireValidPersonalData()
+
         return CoronaTestQRCode.RapidAntigen(
             hash = payload.hash,
             createdAt = payload.createdAt,
@@ -29,14 +32,14 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona
         )
     }
 
-    private fun extractData(rawString: String): Payload {
+    private fun extractData(rawString: String): RawPayload {
         return rawString
             .removePrefix(PREFIX1)
             .removePrefix(PREFIX2)
             .decode()
     }
 
-    private fun String.decode(): Payload {
+    private fun String.decode(): RawPayload {
         val decoded = if (
             this.contains("+") ||
             this.contains("/") ||
@@ -49,54 +52,56 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona
         return Gson().fromJson(decoded.commonToUtf8String())
     }
 
-    private data class Payload(
-        @SerializedName("hash")
-        val rawHash: String?,
-        @SerializedName("timestamp")
-        val rawTimestamp: Long?,
-        @SerializedName("fn")
-        val rawFirstName: String?,
-        @SerializedName("ln")
-        val rawLastName: String?,
-        @SerializedName("dob")
-        val rawDateOfBirth: String?
-    ) {
-        val hash: String
-            get() {
-                if (rawHash == null || !rawHash.isSha256Hash()) throw InvalidQRCodeException("Hash is invalid")
-                return rawHash
-            }
+    private data class RawPayload(
+        @SerializedName("hash") val hash: String?,
+        @SerializedName("timestamp") val timestamp: Long?,
+        @SerializedName("fn") val firstName: String?,
+        @SerializedName("ln") val lastName: String?,
+        @SerializedName("dob") val dateOfBirth: String?
+    )
 
-        val createdAt: Instant
-            get() {
-                if (rawTimestamp == null || rawTimestamp <= 0) throw InvalidQRCodeException("Timestamp is invalid")
-                return Instant.ofEpochSecond(rawTimestamp)
-            }
+    private data class CleanPayload(val raw: RawPayload) {
 
-        val firstName: String?
-            get() {
-                if (rawFirstName.isNullOrEmpty()) return null
-                return rawFirstName
-            }
+        val hash: String by lazy {
+            if (raw.hash == null || !raw.hash.isSha256Hash()) throw InvalidQRCodeException("Hash is invalid")
+            raw.hash
+        }
 
-        val lastName: String?
-            get() {
-                if (rawLastName.isNullOrEmpty()) return null
-                return rawLastName
-            }
+        val createdAt: Instant by lazy {
+            if (raw.timestamp == null || raw.timestamp <= 0) throw InvalidQRCodeException("Timestamp is invalid")
+            Instant.ofEpochSecond(raw.timestamp)
+        }
 
-        val dateOfBirth: LocalDate?
-            get() {
-                if (rawDateOfBirth.isNullOrEmpty()) return null
-                return try {
-                    LocalDate.parse(rawDateOfBirth)
-                } catch (e: Exception) {
-                    Timber.e("Invalid date format")
-                    throw InvalidQRCodeException(
-                        "Date of birth has wrong format: $rawDateOfBirth. It should be YYYY-MM-DD"
-                    )
-                }
+        val firstName: String? by lazy {
+            if (raw.firstName.isNullOrEmpty()) null else raw.firstName
+        }
+
+        val lastName: String? by lazy {
+            if (raw.lastName.isNullOrEmpty()) null else raw.lastName
+        }
+
+        val dateOfBirth: LocalDate? by lazy {
+            if (raw.dateOfBirth.isNullOrEmpty()) return@lazy null
+
+            try {
+                LocalDate.parse(raw.dateOfBirth)
+            } catch (e: Exception) {
+                Timber.e("Invalid date format")
+                throw InvalidQRCodeException(
+                    "Date of birth has wrong format: ${raw.dateOfBirth}. It should be YYYY-MM-DD"
+                )
             }
+        }
+
+        fun requireValidPersonalData() {
+            val allOrNothing = listOf(
+                firstName != null,
+                lastName != null,
+                dateOfBirth != null,
+            )
+            val complete = allOrNothing.all { it } || allOrNothing.all { !it }
+            if (!complete) throw InvalidQRCodeException("QRCode contains incomplete personal data: $raw")
+        }
     }
 
     companion object {
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractorTest.kt
index b01ffcd1951f42d090d8ffebd378b5a57c7df64a..459fff1d3ceb51ffacb9d2a6d1246e1ffe5aa298 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractorTest.kt
@@ -1,6 +1,7 @@
 package de.rki.coronawarnapp.coronatest.qrcode
 
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import io.kotest.assertions.throwables.shouldThrow
 import io.kotest.matchers.shouldBe
 import org.joda.time.Instant
 import org.joda.time.LocalDate
@@ -53,4 +54,9 @@ class RapidAntigenQrCodeExtractorTest : BaseTest() {
         data.lastName shouldBe null
         data.firstName shouldBe null
     }
+
+    @Test
+    fun `personal data is only valid if complete or completely missing`() {
+        shouldThrow<InvalidQRCodeException> { instance.extract(raQrIncompletePersonalData) }
+    }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/TestQrCodes.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/TestQrCodes.kt
index b23da88efe7bbadd9970e4a2b5e7ea110aa06641..c439951382bf5a228ab7b2a4f89b3c96611aff52 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/TestQrCodes.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/TestQrCodes.kt
@@ -32,3 +32,15 @@ internal val raQrCode8 =
 // }
 internal val raQrCodeEmptyStrings =
     "https://s.coronawarn.app?v=1#ewogICAgImZuIjoiIiwKICAgICJsbiI6IiIsCiAgICAiZG9iIjoiIiwKICAgICJ0ZXN0aWQiOiIwZTYwMGI0Mi1jYzIxLTRlNzUtOTE0Yy03MDYwMzE0M2I2Y2IiLAogICAgInRpbWVzdGFtcCI6IDE2MTkwMTI5NTIsCiAgICAic2FsdCI6IjUxMGYzZDc1MGMyZmM2MzFmYmNmZTMyY2IwNmJiYmE5IiwKICAgICJoYXNoIjoiZDZlNGQwMTgxZDgxMDliZjA1YjM0NmEwZDJlMGVmMGNjNDcyZWVkNzBkOWRmOGM0YjlhZTVjN2EwMDlmM2UzNCIKfQ"
+
+// {
+//     "fn":"Max",
+//     "ln":"",
+//     "dob":"",
+//     "testid":"0e600b42-cc21-4e75-914c-70603143b6cb",
+//     "timestamp": 1619012952,
+//     "salt":"510f3d750c2fc631fbcfe32cb06bbba9",
+//     "hash":"d6e4d0181d8109bf05b346a0d2e0ef0cc472eed70d9df8c4b9ae5c7a009f3e34"
+// }
+internal val raQrIncompletePersonalData =
+    "https://s.coronawarn.app?v=1#ewogICAgImZuIjoiTWF4IiwKICAgICJsbiI6IiIsCiAgICAiZG9iIjoiIiwKICAgICJ0ZXN0aWQiOiIwZTYwMGI0Mi1jYzIxLTRlNzUtOTE0Yy03MDYwMzE0M2I2Y2IiLAogICAgInRpbWVzdGFtcCI6IDE2MTkwMTI5NTIsCiAgICAic2FsdCI6IjUxMGYzZDc1MGMyZmM2MzFmYmNmZTMyY2IwNmJiYmE5IiwKICAgICJoYXNoIjoiZDZlNGQwMTgxZDgxMDliZjA1YjM0NmEwZDJlMGVmMGNjNDcyZWVkNzBkOWRmOGM0YjlhZTVjN2EwMDlmM2UzNCIKfQ"