{"id":15150256,"url":"https://github.com/google/capillary","last_synced_at":"2025-04-06T17:11:57.265Z","repository":{"id":65981080,"uuid":"135332674","full_name":"google/capillary","owner":"google","description":"Capillary is a library to simplify the sending of end-to-end encrypted push messages from Java-based application servers to Android clients.","archived":false,"fork":false,"pushed_at":"2018-12-12T17:57:31.000Z","size":942,"stargazers_count":495,"open_issues_count":6,"forks_count":41,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-03-30T15:11:11.764Z","etag":null,"topics":["android","crypto","cryptography","end-to-end-encryption","java","privacy","security"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/google.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-05-29T17:47:21.000Z","updated_at":"2025-03-23T16:52:41.000Z","dependencies_parsed_at":"2023-02-19T18:45:36.927Z","dependency_job_id":null,"html_url":"https://github.com/google/capillary","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fcapillary","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fcapillary/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fcapillary/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/google%2Fcapillary/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/google","download_url":"https://codeload.github.com/google/capillary/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247517914,"owners_count":20951719,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","crypto","cryptography","end-to-end-encryption","java","privacy","security"],"created_at":"2024-09-26T14:02:08.967Z","updated_at":"2025-04-06T17:11:57.233Z","avatar_url":"https://github.com/google.png","language":"Java","readme":"# Capillary\n\nThis is a library to simplify the sending of end-to-end (E2E) encrypted push messages from\nJava-based application servers to Android clients. Please check the instructions below and the\n[demo](demo) for more details.\n\n## Installation\n\nTo add a dependency using Maven:\n\n- For a Java-based server:\n  ```xml\n  \u003cdependency\u003e\n    \u003cgroupId\u003ecom.google.capillary\u003c/groupId\u003e\n    \u003cartifactId\u003elib\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.0\u003c/version\u003e\n  \u003c/dependency\u003e\n  ```\n\n- For Android:\n  ```xml\n  \u003cdependency\u003e\n    \u003cgroupId\u003ecom.google.capillary\u003c/groupId\u003e\n    \u003cartifactId\u003elib-android\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.0\u003c/version\u003e\n  \u003c/dependency\u003e\n  ```\n\nTo add a dependency using Gradle:\n\n- For a Java-based server:\n  ```\n  dependencies {\n    compile 'com.google.capillary:lib:1.0.0'\n  }\n  ```\n- For Android:\n  ```\n  dependencies {\n    compile 'com.google.capillary:lib-android:1.0.0'\n  }\n  ```\n\n## API docs\n\n- Java:\n  - [1.0.0](https://google.github.com/capillary/javadoc/lib/1.0.0)\n- Android:\n  - [1.0.0](https://google.github.com/capillary/javadoc/lib-android/1.0.0)\n\n## Introduction\n\nTo use push messaging services to send messages to connected devices, developers must send them\nthrough a third party messaging service, such as\n[Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/) (FCM).\nIt’s simple to encrypt message contents between the developer and the messaging service using https.\nMajor messaging services, including [FCM](https://firebase.google.com/docs/cloud-messaging/), also\nencrypt messages between their servers and client devices.\n\nHowever, messages between the developer server and the user devices are not encrypted\nend-to-end (E2E):\n\n![no e2ee](img/no_e2ee.png)\n\nE2E encryption can be achieved by generating an asymmetric encryption key pair on the client,\nregistering the public key with the developer messaging service, encrypting outgoing messages with\nthe public key, and decrypting messages on the client using the private key:\n\n![with capillary](img/with_capillary.png)\n\nCapillary handles these operations for push messaging services used by Android apps. It includes:\n\n- Crypto functionality and key management across all versions of Android back to\n[KitKat](https://www.android.com/versions/kit-kat-4-4/) (API level 19).\n\n- Key generation and registration workflows.\n\n- Message encryption (on the server) and decryption (on the client).\n\n- Integrity protection to prevent message modification.\n\n- Edge-cases, such as users adding/resetting device lock after installing the app, users resetting\napp storage, etc.\n\nAs a bonus, it also allows developers to require that devices are unlocked before selected messages\ncan be decrypted. This includes messages on devices using\n[File-Based Encryption](https://source.android.com/security/encryption/file-based) (FBE):\nencrypted messages are cached in Device Encrypted (DE) storage and message decryption keys are\nstored in\n[Android keystore](https://developer.android.com/training/articles/keystore.html) requiring\n[user authentication](https://developer.android.com/training/articles/keystore#UserAuthentication).\nThis allows developers to specify messages with sensitive content to remain encrypted in cached form\nuntil the user has unlocked and decrypted their device.\n\n## API Options\n\n### Web Push vs RSA-ECDSA\n\n- Web Push\n  \n  **Pro:** Follows the [IETF RFC 8291](https://tools.ietf.org/html/rfc8291), therefore allows\n  developers to share code and key storage infrastructure with existing Web Push implementations.\n  Web Push protocol is based on the Elliptic-curve Diffie-Hellman (ECDH) key exchange algorithm,\n  which is highly efficient for performance-constrained devices. Note that apps (as opposed to\n  browsers) cannot receive raw Web Push messages through FCM, but Web Push messages can easily be\n  wrapped in the appropriate FCM JSON by a proxy implementation, allowing you to use the same\n  infrastructure with minor modifications.\n  \n  **Con:** Android Keystore does not support ECDH key operations. Keys are hybrid-encrypted with an\n  RSA key stored in keystore meaning that EC private key plaintext is available in user memory\n  during crypto operations.\n\n- RSA-ECDSA\n  \n  **Pro:** Hybrid-encrypts a message with a client-generated RSA public key (for confidentiality)\n  and signs the ciphertext with a developer-generated ECDSA public key (for integrity). RSA crypto\n  operations (encrypt, decrypt) are supported by Android Keystore from SDK versions 18 (Jelly Bean)\n  and above, meaning key material is not available outside of the trusted execution environment.\n  This means even a sophisticated attacker with access to the device memory cannot access private\n  key material (for example, to decrypt future messages arriving in Direct Boot mode).\n  \n  **Con:** Less efficient than ECDH and keys are not compatible with Web Push messaging standard.\n\n### Auth vs NoAuth\n\nAuth bound keys ensures that messages cannot be read by users when their device is locked, meaning\nsensitive content will not be readable by shoulder-surfers or if the device is lost or stolen.\n\n## API Overview\n\nCapillary provides the core crypto functionality required to send (from an application server) and\nreceive encrypted push messages in Android apps. This covers:\n\n- Generating and storing keys on the client.\n\n- Encrypting and signing messages on the server.\n\n- Decrypting and verifying encrypted messages on the client.\n\n- Identifying encrypted messages that should be stored for later if received while the device is\n  locked.\n\nBecause server-side architectures and push messaging use-cases are many and varied, it is not\npractical to provide a server-side API to handle all possible push message implementations.\nTherefore, we have decoupled the crypto functionality above from message transmission and\nserver-side key storage/retrieval functions. We have, however, provided a full-stack implementation\nthat uses Capillary to send E2E-encrypted push messages from a Java-based server to Android clients\nin the [demo application](demo). In summary, you will need to implement the following aspects of the\nsolution yourself (using the [demo application](demo) and instructions below for guidance where\nrequired):\n\n- Registering public keys generated by Capillary with your application server.\n- (on server) Indexing the public keys against your users/devices such that you can easily retrieve\n  them them to encrypt message.\n- Sending messages encrypted using Capillary to devices. Our [demo application](demo) uses FCM. But\n  Capillary can be used with other push messaging services too.\n- Passing encrypted push messages to Capillary for decryption.\n- Requesting Capillary to decrypt any cached ciphertexts (i.e., those that were received while the\n  device was locked) once the device is in an authenticated context (i.e., the users has unlocked\n  the screen).\n- Displaying or otherwise handling the messages decrypted by the Capillary library.\n\n## API Integration\n\nPlease follow the following steps to integrate with the Capillary library.\n\n### Prerequisites\n\nBefore the Capillary library can be used, it must be initialized at runtime as follows:\n```java\nimport com.google.capillary.Config;\n\nConfig.initialize();\n```\n\nIf you are using RSA-ECDSA algorithm, you need to generate an ECDSA public/private key pair and make\nthe public key available to your Android app (e.g., as a\n[raw resource](https://developer.android.com/guide/topics/resources/providing-resources))\nand private key available to your application server. Use the [utility program](tools) that we have\nprovided to generate such ECDSA key pairs:\n```shell\n$ ./gradlew tools:installDist\n$ ./tools/build/install/tools/bin/ecdsa-key-pair-generator \\\n\u003e --ecdsa_public_key_path=\u003cpath to new public key\u003e \\\n\u003e --ecdsa_private_key_path=\u003cpath to new private key\u003e\n```\n\n### On Android Clients\n\n#### Capillary Handler Implementation\n\nThe Capillary library uses methods of the \n[`CapillaryHandler`](lib-android/src/main/java/com/google/capillary/android/CapillaryHandler.java)\ninterface to provide responses, such as public keys, decrypted plaintexts, etc., back to the Android\napp. Therefore, the first step in integrating an Android app with the Capillary library is to\nimplement the\n[`CapillaryHandler`](lib-android/src/main/java/com/google/capillary/android/CapillaryHandler.java)\ninterface with your app-specific logic to handle the responses mentioned above. You can see how\nthe Capillary library's [demo Android app](demo/android) implements the\n[`CapillaryHandler`](lib-android/src/main/java/com/google/capillary/android/CapillaryHandler.java) interface\nin [`DemoCapillaryHandler`](demo/android/src/main/java/com/google/capillary/demo/android/DemoCapillaryHandler.java) class.\n\n#### Key Generation\n\nEach Capillary key pair is identified by a key pair ID (aka keychain ID), which is an arbitrary\nstring that is up to you to decide. To generate a key pair:\n```java\nimport android.content.Context;\nimport com.google.capillary.android.RsaEcdsaKeyManager;\nimport com.google.capillary.android.WebPushKeyManager;\nimport java.io.InputStream;\n\nContext context = ... // The current app context.\nString keychainId = ... // Some identifier for the key pair.\nboolean isAuth = ... // Whether the private key usage should be guarded by the device lock.\n\n// To generate an RSA-ECDSA key pair.\nInputStream senderVerificationKey = ... // The ECDSA public key of the server.\nRsaEcdsaKeyManager.getInstance(context, keychainId, senderVerificationKey).generateKeyPair(isAuth);\n\n// To generate a Web Push key pair.\nWebPushKeyManager.getInstance(context, keychainId).generateKeyPair(isAuth);\n```\n\nThere is also a [generateKeyPairs](lib-android/src/main/java/com/google/capillary/android/KeyManager.java)\nmethod to generate both Auth and NoAuth keys in a single method call.\n\n#### Public Key Retrieval\nAfter generating a Capillary key pair, you can retrieve the generated public key in a byte array as\nfollows:\n```java\nimport android.content.Context;\nimport com.google.capillary.android.CapillaryHandler;\nimport com.google.capillary.android.RsaEcdsaKeyManager;\nimport com.google.capillary.android.WebPushKeyManager;\nimport java.io.InputStream;\n\nContext context = ... // The current app context.\nString keychainId = ... // The identifier for the key pair.\nboolean isAuth = ... // Whether the private key usage is guarded by the device lock.\nCapillaryHandler handler = ... // An implementation of CapillaryHandler interface.\nObject extra = ... // Any extra information to be passed back to the handler.\n\n// To obtain an RSA-ECDSA public key.\nInputStream senderVerificationKey = ... // The ECDSA public key of the server.\nRsaEcdsaKeyManager.getInstance(context, keychainId, senderVerificationKey)\n    .getPublicKey(isAuth, handler, extra);\n\n// To obtain a Web Push public key.\nWebPushKeyManager.getInstance(context, keychainId).getPublicKey(isAuth, handler, extra);\n\n// The Capillary library returns a byte array representing the Capillary public key via the\n// handlePublicKey method of the CapillaryHandler instance.\n```\n\n#### Decryption\n\nAfter receiving a ciphertext generated using a Capillary public key, you can decrypt it as follows:\n```java\nimport android.content.Context;\nimport com.google.capillary.android.CapillaryHandler;\nimport com.google.capillary.android.RsaEcdsaKeyManager;\nimport com.google.capillary.android.WebPushKeyManager;\nimport java.io.InputStream;\n\nbyte[] ciphertext = ... // The ciphertext received through FCM.\nContext context = ... // The current app context.\nString keychainId = ... // The identifier for the key pair.\nCapillaryHandler handler = ... // An implementation of CapillaryHandler interface.\nObject extra = ... // Any extra information to be passed back to the handler.\n\n// To decrypt a ciphertext and pass the plaintext to the CapillaryHandler instance,\n// (e.g. for display to the user):\n\n// For RSA-ECDSA:\nInputStream senderVerificationKey = ... // The ECDSA public key of the server.\nRsaEcdsaKeyManager.getInstance(context, keychainId, senderVerificationKey)\n    .getDecrypterManager().decrypt(ciphertext, handler, extra);\n\n// For Web Push:\nWebPushKeyManager.getInstance(context, keychainId)\n    .getDecrypterManager().decrypt(ciphertext, handler, extra);\n\n// The Capillary library returns a byte array representing the plaintext via the handleData\n// method of the CapillaryHandler instance.\n```\n\nKeep in mind that during decryption, the Capillary library may automatically re-generate\nthe underlying Capillary key pairs if those key pairs are irrecoverably corrupted, which can happen,\nfor example, when the user adds/resets the device lock, resets app storage, etc. Such a newly\ngenerated public key along with the Capillary ciphertext bytes that triggered key re-generation\nwill be passed to the Android app via the appropriate methods of the\n[`CapillaryHandler`](lib-android/src/main/java/com/google/capillary/android/CapillaryHandler.java).\n\nIf the ciphertext has been generated using an Auth key but the Android device is in an\nunauthenticated context, the Capillary library internally saves the ciphertext to be decrypted later\nand informs the Android app via the\n[authCiphertextSavedForLater](lib-android/src/main/java/com/google/capillary/android/CapillaryHandler.java)\nmethod. This allows the Android app to handle cached ciphertexts, e.g. by telling the user messages\nare available upon unlock. Upon the user unlocking the device, you can have the Capillary library\ndecrypt any saved ciphertexts as follows:\n```java\nimport android.content.Context;\nimport com.google.capillary.android.CapillaryHandler;\nimport com.google.capillary.android.RsaEcdsaKeyManager;\nimport com.google.capillary.android.WebPushKeyManager;\nimport java.io.InputStream;\n\nContext context = ... // The current app context.\nString keychainId = ... // The identifier for the key pair.\nCapillaryHandler handler = ... // An implementation of CapillaryHandler interface.\nObject extra = ... // Any extra information to be passed back to the handler.\n\n// To decrypt saved ciphertexts and pass the plaintexts to the CapillaryHandler instance,\n// (e.g. for display to the user):\n\n// For RSA-ECDSA:\nInputStream senderVerificationKey = ... // The ECDSA public key of the server.\nRsaEcdsaKeyManager.getInstance(context, keychainId, senderVerificationKey)\n    .getDecrypterManager().decryptSaved(handler, extra);\n\n// For Web Push:\nWebPushKeyManager.getInstance(context, keychainId)\n    .getDecrypterManager().decryptSaved(handler, extra);\n\n// For each decrypted ciphertext, the Capillary library returns a byte array representing the\n// plaintext via the handleData method of the CapillaryHandler instance.\n```\n\nThere are several ways to trigger the handler for cached ciphertext upon device unlock. The approach\nused by the Capillary library's [demo Android app](demo/android) is to listen for the\n[ACTION_USER_PRESENT](https://developer.android.com/reference/android/content/Intent.html#ACTION_USER_PRESENT)\nbroadcast intent. See\n[`DeviceUnlockedBroadcastReceiver`](android/src/main/java/com/google/capillary/demo/android/DeviceUnlockedBroadcastReceiver.java)\nfor more details.\n\n#### Key Deletion\n\nTo delete a Capillary key pair:\n```java\nimport android.content.Context;\nimport com.google.capillary.android.RsaEcdsaKeyManager;\nimport com.google.capillary.android.WebPushKeyManager;\nimport java.io.InputStream;\n\nContext context = ... // The current app context.\nString keychainId = ... // The identifier for the key pair.\nboolean isAuth = ... // Whether the private key usage is guarded by the device lock.\n\n// To delete an RSA-ECDSA key pair.\nInputStream senderVerificationKey = ... // The ECDSA public key of the server.\nRsaEcdsaKeyManager.getInstance(context, keychainId, senderVerificationKey).deleteKeyPair(isAuth);\n\n// To delete a Web Push key pair.\nWebPushKeyManager.getInstance(context, keychainId).deleteKeyPair(isAuth);\n```\n\n### On Java Application Servers\n\nThe Capillary library provides the functionality to encrypt messages on Java-based application\nservers. \n\n#### Encryption\n\nTo encrypt a message using a Capillary public key:\n\n```java\nimport com.google.capillary.EncrypterManager;\nimport com.google.capillary.RsaEcdsaEncrypterManager;\nimport com.google.capillary.WebPushEncrypterManager;\nimport java.io.InputStream;\n\nbyte[] recipientPublicKey = ... // The Capillary public key of the client.\nbyte[] message = ... // The message to be sent to the client.\n\n// To create an RSA-ECDSA ciphertext.\nInputStream senderSigningKey = ... // The ECDSA private key of the server.\nEncrypterManager rsaEcdsaEncrypterManager = new RsaEcdsaEncrypterManager(senderSigningKey);\nrsaEcdsaEncrypterManager.loadPublicKey(recipientPublicKey);\nbyte[] ciphertext = rsaEcdsaEncrypterManager.encrypt(message);\n// This step is not strictly necessary, but it ensures that the EncrypterManager releases the\n// stored public key for garbage collection.\nrsaEcdsaEncrypterManager.clearPublicKey();\n\n// To create a Web Push ciphertext.\nEncrypterManager webPushEncrypterManager = new WebPushEncrypterManager();\nwebPushEncrypterManager.loadPublicKey(recipientPublicKey);\nbyte[] ciphertext = webPushEncrypterManager.encrypt(message);\nwebPushEncrypterManager.clearPublicKey();\n```\n\n## Maintainers\n\nThe Capillary library is maintained by the following Googlers:\n\n- [Milinda Perera](https://milinda-perera.com), Software Engineer\n- [Giles Hogben](http://www.gilestv.com/frallet/), Privacy Engineer\n","funding_links":[],"categories":["Awesome Mobile Application Penetration Testing  ![awesome](https://awesome.re/badge.svg)"],"sub_categories":["Android Application Penetration Testing"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Fcapillary","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoogle%2Fcapillary","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogle%2Fcapillary/lists"}