In the world of constrained devices, IoT sensors, and efficient web APIs, JSON's verbosity often becomes a liability. CBOR (Concise Binary Object Representation) offers a compact binary alternative that maintains the flexibility of JSON while significantly reducing message sizes. But with compactness comes the need for security. COSE (CBOR Object Signing and Encryption) provides a standards-based approach to securing CBOR data structures, offering digital signatures, message authentication codes, and encryption—all optimized for constrained environments. For Java developers building IoT applications, mobile backends, or efficient APIs, COSE represents the next evolution in data security.
What is COSE?
COSE (RFC 8152) is a specification for creating compact security objects using CBOR. It defines:
- Signing: Digital signatures using algorithms like ECDSA, EdDSA, and RSA
- MAC: Message Authentication Codes for symmetric authentication
- Encryption: Both symmetric and asymmetric encryption for confidentiality
- Key Management: Structures for exchanging and identifying cryptographic keys
COSE is the foundation for several important standards:
- WebAuthn (FIDO2): Uses COSE for credential representations
- CWT (CBOR Web Tokens): The CBOR equivalent of JWT
- OSCORE: Object Security for Constrained RESTful Environments
Why COSE Matters for Java
- IoT and Edge Computing: Efficient security for resource-constrained devices
- Bandwidth Efficiency: Significantly smaller than JOSE (JWT/JWE/JWS)
- Standards Compliance: IETF standard with broad industry adoption
- Binary Safety: No text encoding issues, direct binary processing
- Hardware Integration: Well-suited for hardware security modules
COSE Architecture
COSE Object Structure: ┌─────────────────────────────────────────────┐ │ COSE_Message │ ├─────────────────────────────────────────────┤ │ Protected Headers (authenticated/encrypted) │ ├─────────────────────────────────────────────┤ │ Unprotected Headers (plaintext metadata) │ ├─────────────────────────────────────────────┤ │ Payload (the actual data) │ ├─────────────────────────────────────────────┤ │ Signature / MAC / Tag │ └─────────────────────────────────────────────┘ COSE Modes: - COSE_Sign: Digital signatures (public key) - COSE_Mac: Message Authentication Codes (symmetric) - COSE_Encrypt: Encryption (symmetric) - COSE_Encrypt0: Simplified encryption - COSE_Sign1: Simplified signature - COSE_Mac0: Simplified MAC
Implementing COSE in Java
1. Dependencies and Setup
<dependencies> <!-- COSE implementation --> <dependency> <groupId>com.augustcellars.cose</groupId> <artifactId>cose-java</artifactId> <version>1.1.0</version> </dependency> <!-- CBOR processing --> <dependency> <groupId>com.upokecenter</groupId> <artifactId>cbor</artifactId> <version>4.5.2</version> </dependency> <!-- Bouncy Castle for additional algorithms --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> <version>1.77</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk18on</artifactId> <version>1.77</version> </dependency> </dependencies>
2. Basic COSE Sign1 (Single Signer)
import com.augustcellars.cose.*;
import com.augustcellars.cose.CoseException;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;
/**
* Basic COSE Sign1 implementation for single signatures
*/
public class COSESign1Example {
public static class Sign1Result {
private final byte[] coseMessage;
private final String algorithm;
public Sign1Result(byte[] coseMessage, String algorithm) {
this.coseMessage = coseMessage.clone();
this.algorithm = algorithm;
}
public byte[] getCoseMessage() { return coseMessage.clone(); }
public String getAlgorithm() { return algorithm; }
public String getBase64() { return Base64.getUrlEncoder().encodeToString(coseMessage); }
}
/**
* Generate an EC key pair for COSE signing
*/
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); // P-256
keyGen.initialize(ecSpec, new SecureRandom());
return keyGen.generateKeyPair();
}
/**
* Create a COSE Sign1 message
*/
public static Sign1Result signMessage(byte[] payload, PrivateKey privateKey,
String keyId) throws CoseException {
// Create Sign1 message
Sign1Message message = new Sign1Message();
// Set payload
message.SetContent(payload);
// Add protected headers
message.addAttribute(HeaderKeys.Algorithm,
AlgorithmID.ECDSA_256.AsCBOR(),
Attribute.PROTECTED);
// Add unprotected headers
if (keyId != null && !keyId.isEmpty()) {
message.addAttribute(HeaderKeys.KID,
keyId.getBytes(),
Attribute.UNPROTECTED);
}
// Sign the message
message.sign(privateKey);
// Encode to CBOR
byte[] encoded = message.EncodeToBytes();
return new Sign1Result(encoded, "ES256");
}
/**
* Verify a COSE Sign1 message
*/
public static byte[] verifyMessage(byte[] coseMessage, PublicKey publicKey)
throws CoseException {
// Decode message
Sign1Message message = (Sign1Message) Message.DecodeFromBytes(coseMessage);
// Verify signature
if (!message.validate(publicKey)) {
throw new SecurityException("Signature validation failed");
}
// Return payload
return message.GetContent();
}
public static void main(String[] args) throws Exception {
// Generate key pair
KeyPair keyPair = generateKeyPair();
System.out.println("Key pair generated");
// Create payload
String payload = "Hello, COSE! This is a signed message.";
byte[] payloadBytes = payload.getBytes("UTF-8");
System.out.println("Payload: " + payload);
// Sign the message
Sign1Result signed = signMessage(
payloadBytes,
keyPair.getPrivate(),
"key-12345"
);
System.out.println("COSE Sign1 (Base64): " + signed.getBase64());
System.out.println("Message size: " + signed.getCoseMessage().length + " bytes");
// Verify the message
byte[] verifiedPayload = verifyMessage(signed.getCoseMessage(), keyPair.getPublic());
String verified = new String(verifiedPayload, "UTF-8");
System.out.println("Verified payload: " + verified);
// Tamper with the message and verify failure
byte[] tampered = signed.getCoseMessage().clone();
tampered[tampered.length - 1] ^= 0x01; // Flip last bit
try {
verifyMessage(tampered, keyPair.getPublic());
System.out.println("ERROR: Tampering not detected!");
} catch (SecurityException e) {
System.out.println("✓ Tampering detected: " + e.getMessage());
}
}
}
3. COSE Encrypt0 (Single Recipient Encryption)
import com.augustcellars.cose.*;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
/**
* COSE Encrypt0 for symmetric encryption
*/
public class COSEEncrypt0Example {
public static class Encrypt0Result {
private final byte[] coseMessage;
private final byte[] iv;
private final String algorithm;
public Encrypt0Result(byte[] coseMessage, byte[] iv, String algorithm) {
this.coseMessage = coseMessage.clone();
this.iv = iv.clone();
this.algorithm = algorithm;
}
public byte[] getCoseMessage() { return coseMessage.clone(); }
public byte[] getIv() { return iv.clone(); }
public String getAlgorithm() { return algorithm; }
public String getBase64() { return Base64.getUrlEncoder().encodeToString(coseMessage); }
}
/**
* Generate an AES key for encryption
*/
public static SecretKey generateKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256, new SecureRandom());
return keyGen.generateKey();
}
/**
* Encrypt a message using COSE Encrypt0
*/
public static Encrypt0Result encryptMessage(byte[] plaintext, SecretKey key,
byte[] associatedData) throws CoseException {
// Create Encrypt0 message
Encrypt0Message message = new Encrypt0Message();
// Set content (will be encrypted)
message.SetContent(plaintext);
// Add protected headers
message.addAttribute(HeaderKeys.Algorithm,
AlgorithmID.AES_GCM_256.AsCBOR(),
Attribute.PROTECTED);
// Generate random IV
byte[] iv = new byte[12]; // 96 bits for GCM
new SecureRandom().nextBytes(iv);
message.addAttribute(HeaderKeys.IV, iv, Attribute.UNPROTECTED);
// Add associated data as unprotected (but authenticated via AAD)
if (associatedData != null && associatedData.length > 0) {
message.addAttribute(HeaderKeys.AAD, associatedData, Attribute.UNPROTECTED);
}
// Encrypt
message.encrypt(key);
// Encode to CBOR
byte[] encoded = message.EncodeToBytes();
return new Encrypt0Result(encoded, iv, "A256GCM");
}
/**
* Decrypt a COSE Encrypt0 message
*/
public static byte[] decryptMessage(byte[] coseMessage, SecretKey key)
throws CoseException {
// Decode message
Encrypt0Message message = (Encrypt0Message) Message.DecodeFromBytes(coseMessage);
// Decrypt
byte[] plaintext = message.decrypt(key);
return plaintext;
}
public static void main(String[] args) throws Exception {
// Generate encryption key
SecretKey key = generateKey();
System.out.println("Encryption key generated");
// Original message
String plaintext = "Sensitive data that needs encryption";
byte[] plaintextBytes = plaintext.getBytes("UTF-8");
byte[] associatedData = "context=transaction-123".getBytes("UTF-8");
System.out.println("Plaintext: " + plaintext);
System.out.println("Associated data: " + new String(associatedData, "UTF-8"));
// Encrypt
Encrypt0Result encrypted = encryptMessage(
plaintextBytes,
key,
associatedData
);
System.out.println("COSE Encrypt0 (Base64): " + encrypted.getBase64());
System.out.println("Message size: " + encrypted.getCoseMessage().length + " bytes");
// Decrypt
byte[] decryptedBytes = decryptMessage(encrypted.getCoseMessage(), key);
String decrypted = new String(decryptedBytes, "UTF-8");
System.out.println("Decrypted: " + decrypted);
// Wrong key should fail
SecretKey wrongKey = generateKey();
try {
decryptMessage(encrypted.getCoseMessage(), wrongKey);
System.out.println("ERROR: Wrong key accepted!");
} catch (CoseException e) {
System.out.println("✓ Wrong key rejected: " + e.getMessage());
}
}
}
4. COSE Mac0 (Message Authentication Code)
import com.augustcellars.cose.*;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.SecureRandom;
import java.util.Base64;
/**
* COSE Mac0 for symmetric authentication
*/
public class COSEMac0Example {
public static class Mac0Result {
private final byte[] coseMessage;
private final byte[] mac;
private final String algorithm;
public Mac0Result(byte[] coseMessage, byte[] mac, String algorithm) {
this.coseMessage = coseMessage.clone();
this.mac = mac.clone();
this.algorithm = algorithm;
}
public byte[] getCoseMessage() { return coseMessage.clone(); }
public byte[] getMac() { return mac.clone(); }
public String getAlgorithm() { return algorithm; }
public String getBase64() { return Base64.getUrlEncoder().encodeToString(coseMessage); }
}
/**
* Generate an HMAC key
*/
public static SecretKey generateKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("HmacSHA256");
keyGen.init(new SecureRandom());
return keyGen.generateKey();
}
/**
* Create a COSE Mac0 message
*/
public static Mac0Result createMac(byte[] payload, SecretKey key,
String keyId) throws CoseException {
// Create Mac0 message
Mac0Message message = new Mac0Message();
// Set content (not encrypted, just authenticated)
message.SetContent(payload);
// Add protected headers
message.addAttribute(HeaderKeys.Algorithm,
AlgorithmID.HMAC_SHA_256.AsCBOR(),
Attribute.PROTECTED);
// Add key ID
if (keyId != null && !keyId.isEmpty()) {
message.addAttribute(HeaderKeys.KID,
keyId.getBytes(),
Attribute.UNPROTECTED);
}
// Compute MAC
message.sign(key);
// Encode to CBOR
byte[] encoded = message.EncodeToBytes();
// Extract MAC (simplified - in practice would parse the message)
byte[] mac = message.GetMAC();
return new Mac0Result(encoded, mac, "HS256");
}
/**
* Verify a COSE Mac0 message
*/
public static boolean verifyMac(byte[] coseMessage, SecretKey key) throws CoseException {
// Decode message
Mac0Message message = (Mac0Message) Message.DecodeFromBytes(coseMessage);
// Verify MAC
return message.validate(key);
}
public static void main(String[] args) throws Exception {
// Generate key
SecretKey key = generateKey();
System.out.println("MAC key generated");
// Create payload
String payload = "Message with integrity protection";
byte[] payloadBytes = payload.getBytes("UTF-8");
System.out.println("Payload: " + payload);
// Create MAC
Mac0Result macResult = createMac(payloadBytes, key, "hmac-key-1");
System.out.println("COSE Mac0 (Base64): " + macResult.getBase64());
System.out.println("Message size: " + macResult.getCoseMessage().length + " bytes");
System.out.println("MAC (hex): " + bytesToHex(macResult.getMac()));
// Verify MAC
boolean valid = verifyMac(macResult.getCoseMessage(), key);
System.out.println("MAC valid: " + valid);
// Tamper with message
byte[] tampered = macResult.getCoseMessage().clone();
tampered[tampered.length / 2] ^= 0x01;
boolean tamperedValid = verifyMac(tampered, key);
System.out.println("Tampered MAC valid: " + tamperedValid);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
5. COSE Multi-Signature (Multiple Signers)
import com.augustcellars.cose.*;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* COSE multi-signature with multiple signers
*/
public class COSEMultiSignExample {
public static class MultiSignResult {
private final byte[] coseMessage;
private final List<String> signerIds;
public MultiSignResult(byte[] coseMessage, List<String> signerIds) {
this.coseMessage = coseMessage.clone();
this.signerIds = new ArrayList<>(signerIds);
}
public byte[] getCoseMessage() { return coseMessage.clone(); }
public List<String> getSignerIds() { return new ArrayList<>(signerIds); }
}
public static class SignerInfo {
private final KeyPair keyPair;
private final String keyId;
private final AlgorithmID algorithm;
public SignerInfo(KeyPair keyPair, String keyId, AlgorithmID algorithm) {
this.keyPair = keyPair;
this.keyId = keyId;
this.algorithm = algorithm;
}
public PrivateKey getPrivateKey() { return keyPair.getPrivate(); }
public PublicKey getPublicKey() { return keyPair.getPublic(); }
public String getKeyId() { return keyId; }
public AlgorithmID getAlgorithm() { return algorithm; }
}
/**
* Create a multi-signature COSE message
*/
public static MultiSignResult signWithMultipleSigners(byte[] payload,
List<SignerInfo> signers)
throws CoseException {
// Create Sign message
SignMessage message = new SignMessage();
// Set payload
message.SetContent(payload);
// Add each signer
for (SignerInfo signer : signers) {
// Create signer structure
Signer signerStruct = new Signer();
// Add protected headers for this signer
signerStruct.addAttribute(HeaderKeys.Algorithm,
signer.getAlgorithm().AsCBOR(),
Attribute.PROTECTED);
// Add key ID
signerStruct.addAttribute(HeaderKeys.KID,
signer.getKeyId().getBytes(),
Attribute.UNPROTECTED);
// Sign with this signer's key
signerStruct.sign(signer.getPrivateKey(), message.GetContent());
// Add to message
message.addSigner(signerStruct);
}
// Encode to CBOR
byte[] encoded = message.EncodeToBytes();
// Collect signer IDs for response
List<String> signerIds = signers.stream()
.map(SignerInfo::getKeyId)
.toList();
return new MultiSignResult(encoded, signerIds);
}
/**
* Verify a multi-signature COSE message
*/
public static boolean verifyMultiSignature(byte[] coseMessage,
List<PublicKey> expectedKeys)
throws CoseException {
// Decode message
SignMessage message = (SignMessage) Message.DecodeFromBytes(coseMessage);
// Get all signers
List<Signer> signers = message.getSignerList();
// Check number of signers matches expected
if (signers.size() != expectedKeys.size()) {
return false;
}
// Verify each signature
for (int i = 0; i < signers.size(); i++) {
Signer signer = signers.get(i);
PublicKey expectedKey = expectedKeys.get(i);
if (!signer.validate(expectedKey)) {
return false;
}
}
return true;
}
public static void main(String[] args) throws Exception {
// Create multiple signers
List<SignerInfo> signers = new ArrayList<>();
// Signer 1: Alice (P-256)
KeyPair keyPair1 = COSESign1Example.generateKeyPair();
signers.add(new SignerInfo(
keyPair1,
"[email protected]",
AlgorithmID.ECDSA_256
));
// Signer 2: Bob (P-256)
KeyPair keyPair2 = COSESign1Example.generateKeyPair();
signers.add(new SignerInfo(
keyPair2,
"[email protected]",
AlgorithmID.ECDSA_256
));
// Signer 3: Charlie (P-384 for variety)
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
keyGen.initialize(new ECGenParameterSpec("secp384r1"), new SecureRandom());
KeyPair keyPair3 = keyGen.generateKeyPair();
signers.add(new SignerInfo(
keyPair3,
"[email protected]",
AlgorithmID.ECDSA_384
));
System.out.println("Created " + signers.size() + " signers");
// Create payload
String payload = "Multi-party agreement document";
byte[] payloadBytes = payload.getBytes("UTF-8");
System.out.println("Payload: " + payload);
// Sign with all signers
MultiSignResult signed = signWithMultipleSigners(payloadBytes, signers);
System.out.println("COSE Multi-Sign message size: " +
signed.getCoseMessage().length + " bytes");
// Verify with all public keys
List<PublicKey> publicKeys = signers.stream()
.map(SignerInfo::getPublicKey)
.toList();
boolean valid = verifyMultiSignature(signed.getCoseMessage(), publicKeys);
System.out.println("All signatures valid: " + valid);
// Verify with missing key should fail
List<PublicKey> partialKeys = publicKeys.subList(0, 2);
boolean partialValid = verifyMultiSignature(signed.getCoseMessage(), partialKeys);
System.out.println("Partial keys valid: " + partialValid);
}
}
6. COSE Encrypt (Multi-Recipient Encryption)
import com.augustcellars.cose.*;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.SecureRandom;
import java.security.KeyPair;
import java.security.interfaces.ECPublicKey;
import java.util.ArrayList;
import java.util.List;
/**
* COSE Encrypt with multiple recipients
*/
public class COSEMultiEncryptExample {
public static class RecipientInfo {
private final byte[] keyId;
private final String algorithm;
private final byte[] encryptedKey;
public RecipientInfo(byte[] keyId, String algorithm, byte[] encryptedKey) {
this.keyId = keyId.clone();
this.algorithm = algorithm;
this.encryptedKey = encryptedKey.clone();
}
}
/**
* Encrypt a message for multiple recipients
*/
public static byte[] encryptForMultipleRecipients(byte[] plaintext,
List<SecretKey> recipientKeys,
List<String> keyIds)
throws CoseException {
// Create content encryption key (CEK)
byte[] cek = new byte[32]; // AES-256 key
new SecureRandom().nextBytes(cek);
// Create Encrypt message
EncryptMessage message = new EncryptMessage();
// Set protected headers for the content
message.addAttribute(HeaderKeys.Algorithm,
AlgorithmID.AES_GCM_256.AsCBOR(),
Attribute.PROTECTED);
// Generate IV for content encryption
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
message.addAttribute(HeaderKeys.IV, iv, Attribute.UNPROTECTED);
// Encrypt content with CEK
message.SetContent(plaintext);
message.encrypt(cek);
// Create a recipient for each key
for (int i = 0; i < recipientKeys.size(); i++) {
SecretKey recipientKey = recipientKeys.get(i);
String keyId = keyIds.get(i);
// Create recipient structure
Recipient recipient = new Recipient();
// Add key ID
recipient.addAttribute(HeaderKeys.KID,
keyId.getBytes(),
Attribute.UNPROTECTED);
// Encrypt CEK with recipient's key
byte[] encryptedKey = encryptKeyWithAES(cek, recipientKey);
recipient.SetContent(encryptedKey);
// Add recipient to message
message.addRecipient(recipient);
}
// Encode to CBOR
return message.EncodeToBytes();
}
/**
* Decrypt a message for a specific recipient
*/
public static byte[] decryptForRecipient(byte[] coseMessage,
SecretKey recipientKey,
String keyId) throws CoseException {
// Decode message
EncryptMessage message = (EncryptMessage) Message.DecodeFromBytes(coseMessage);
// Find the right recipient by key ID
for (Recipient recipient : message.getRecipientList()) {
OneKey key = new OneKey();
// Check if this recipient matches our key ID
byte[] recipientKeyId = (byte[]) recipient.findAttribute(HeaderKeys.KID);
if (recipientKeyId != null && Arrays.equals(recipientKeyId, keyId.getBytes())) {
// Decrypt the CEK with our key
byte[] encryptedKey = recipient.GetContent();
byte[] cek = decryptKeyWithAES(encryptedKey, recipientKey);
// Decrypt the content with the CEK
message.decrypt(cek);
return message.GetContent();
}
}
throw new CoseException("No matching recipient found for key ID: " + keyId);
}
// Simplified key encryption (in practice, use proper key wrapping)
private static byte[] encryptKeyWithAES(byte[] key, SecretKey wrappingKey) {
// Simplified - in production use AES KeyWrap or similar
byte[] result = new byte[key.length];
for (int i = 0; i < key.length; i++) {
result[i] = (byte) (key[i] ^ wrappingKey.getEncoded()[i % wrappingKey.getEncoded().length]);
}
return result;
}
private static byte[] decryptKeyWithAES(byte[] encryptedKey, SecretKey wrappingKey) {
return encryptKeyWithAES(encryptedKey, wrappingKey); // XOR is symmetric
}
public static void main(String[] args) throws Exception {
// Create multiple recipient keys
List<SecretKey> recipientKeys = new ArrayList<>();
List<String> keyIds = new ArrayList<>();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256, new SecureRandom());
for (int i = 0; i < 3; i++) {
recipientKeys.add(keyGen.generateKey());
keyIds.add("recipient-" + i);
}
System.out.println("Created " + recipientKeys.size() + " recipients");
// Original message
String plaintext = "Message for multiple recipients";
byte[] plaintextBytes = plaintext.getBytes("UTF-8");
System.out.println("Plaintext: " + plaintext);
// Encrypt for all recipients
byte[] encrypted = encryptForMultipleRecipients(
plaintextBytes,
recipientKeys,
keyIds
);
System.out.println("Encrypted message size: " + encrypted.length + " bytes");
// Each recipient can decrypt
for (int i = 0; i < recipientKeys.size(); i++) {
byte[] decrypted = decryptForRecipient(
encrypted,
recipientKeys.get(i),
keyIds.get(i)
);
String decryptedText = new String(decrypted, "UTF-8");
System.out.println("Recipient " + i + " decrypted: " + decryptedText);
}
// Wrong key should fail
SecretKey wrongKey = keyGen.generateKey();
try {
decryptForRecipient(encrypted, wrongKey, keyIds.get(0));
System.out.println("ERROR: Wrong key accepted!");
} catch (CoseException e) {
System.out.println("✓ Wrong key rejected: " + e.getMessage());
}
}
}
7. CBOR Web Token (CWT) Implementation
import com.augustcellars.cose.*;
import com.upokecenter.cbor.CBORObject;
import java.time.Instant;
import java.util.*;
/**
* CBOR Web Token (CWT) implementation using COSE
*/
public class CWTExample {
public static class CWT {
private final Map<String, Object> claims;
private final byte[] token;
public CWT(Map<String, Object> claims, byte[] token) {
this.claims = new HashMap<>(claims);
this.token = token.clone();
}
public Map<String, Object> getClaims() { return new HashMap<>(claims); }
public byte[] getToken() { return token.clone(); }
public String getTokenBase64() {
return Base64.getUrlEncoder().withoutPadding().encodeToString(token);
}
}
// CWT claim keys (from RFC 8392)
public static final int CLAIM_ISSUER = 1;
public static final int CLAIM_SUBJECT = 2;
public static final int CLAIM_AUDIENCE = 3;
public static final int CLAIM_EXPIRATION = 4;
public static final int CLAIM_NOT_BEFORE = 5;
public static final int CLAIM_ISSUED_AT = 6;
public static final int CLAIM_CWT_ID = 7;
/**
* Create a signed CWT
*/
public static CWT createSignedCWT(Map<String, Object> claims,
PrivateKey signingKey,
String keyId) throws CoseException {
// Convert claims to CBOR map
CBORObject claimsMap = CBORObject.NewMap();
for (Map.Entry<String, Object> entry : claims.entrySet()) {
Object value = entry.getValue();
if (value instanceof String) {
claimsMap.Add(entry.getKey(), (String) value);
} else if (value instanceof Long) {
claimsMap.Add(entry.getKey(), (Long) value);
} else if (value instanceof Instant) {
claimsMap.Add(entry.getKey(), ((Instant) value).getEpochSecond());
}
}
// Add standard claims with integer keys
if (claims.containsKey("iss")) {
claimsMap.Add(CLAIM_ISSUER, claims.get("iss"));
}
if (claims.containsKey("sub")) {
claimsMap.Add(CLAIM_SUBJECT, claims.get("sub"));
}
if (claims.containsKey("exp")) {
claimsMap.Add(CLAIM_EXPIRATION, claims.get("exp"));
}
if (claims.containsKey("iat")) {
claimsMap.Add(CLAIM_ISSUED_AT, claims.get("iat"));
}
byte[] payload = claimsMap.EncodeToBytes();
// Create Sign1 message
Sign1Message message = new Sign1Message();
message.SetContent(payload);
// Add headers
message.addAttribute(HeaderKeys.Algorithm,
AlgorithmID.ECDSA_256.AsCBOR(),
Attribute.PROTECTED);
if (keyId != null) {
message.addAttribute(HeaderKeys.KID,
keyId.getBytes(),
Attribute.UNPROTECTED);
}
// Add content type (CWT)
message.addAttribute(HeaderKeys.ContentType,
61, // CWT content type
Attribute.UNPROTECTED);
// Sign
message.sign(signingKey);
byte[] token = message.EncodeToBytes();
return new CWT(claims, token);
}
/**
* Verify and decode a CWT
*/
public static Map<String, Object> verifyAndDecodeCWT(byte[] token,
PublicKey verificationKey)
throws CoseException {
// Decode message
Sign1Message message = (Sign1Message) Message.DecodeFromBytes(token);
// Verify signature
if (!message.validate(verificationKey)) {
throw new SecurityException("CWT signature validation failed");
}
// Decode payload
byte[] payload = message.GetContent();
CBORObject claimsMap = CBORObject.DecodeFromBytes(payload);
// Convert to Java map
Map<String, Object> claims = new HashMap<>();
for (CBORObject key : claimsMap.getKeys()) {
CBORObject value = claimsMap.get(key);
// Handle both string and integer keys
String keyStr;
if (key.getType() == CBORObjectType.Integer) {
keyStr = mapIntKeyToString(key.AsInt32());
} else {
keyStr = key.AsString();
}
if (value.getType() == CBORObjectType.Integer) {
claims.put(keyStr, value.AsInt64());
} else if (value.getType() == CBORObjectType.TextString) {
claims.put(keyStr, value.AsString());
}
}
return claims;
}
private static String mapIntKeyToString(int key) {
switch (key) {
case CLAIM_ISSUER: return "iss";
case CLAIM_SUBJECT: return "sub";
case CLAIM_AUDIENCE: return "aud";
case CLAIM_EXPIRATION: return "exp";
case CLAIM_NOT_BEFORE: return "nbf";
case CLAIM_ISSUED_AT: return "iat";
case CLAIM_CWT_ID: return "cti";
default: return "claim_" + key;
}
}
public static void main(String[] args) throws Exception {
// Generate signing key
KeyPair keyPair = COSESign1Example.generateKeyPair();
// Create claims
Map<String, Object> claims = new HashMap<>();
claims.put("iss", "https://auth.example.com");
claims.put("sub", "user-12345");
claims.put("aud", "api.example.com");
claims.put("exp", Instant.now().plusSeconds(3600).getEpochSecond());
claims.put("iat", Instant.now().getEpochSecond());
claims.put("name", "John Doe");
claims.put("email", "[email protected]");
claims.put("role", "admin");
System.out.println("CWT Claims:");
claims.forEach((k, v) -> System.out.println(" " + k + ": " + v));
// Create signed CWT
CWT cwt = createSignedCWT(claims, keyPair.getPrivate(), "signing-key-1");
System.out.println("\nCWT Token (Base64): " + cwt.getTokenBase64());
System.out.println("Token size: " + cwt.getToken().length + " bytes");
// Verify and decode
Map<String, Object> decoded = verifyAndDecodeCWT(
cwt.getToken(),
keyPair.getPublic()
);
System.out.println("\nDecoded claims:");
decoded.forEach((k, v) -> System.out.println(" " + k + ": " + v));
// Compare with JWT for size comparison
String jwtExample = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTEyMzQ1IiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwiZXhwIjoxNzAwMDAwMDAwfQ.signature";
System.out.println("\nJWT example size: " + jwtExample.length() + " chars");
System.out.println("CWT size: " + cwt.getToken().length + " bytes");
System.out.println("Size reduction: ~" +
(100 - (cwt.getToken().length * 100 / jwtExample.length())) + "%");
}
}
8. COSE with Hardware Security Module (HSM) Integration
import com.augustcellars.cose.*;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Arrays;
/**
* COSE integration with HSM (simplified example)
*/
public class COSEWithHSM {
/**
* Interface for HSM operations
*/
public interface HSMProvider {
PrivateKey getPrivateKey(String keyId);
PublicKey getPublicKey(String keyId);
byte[] sign(PrivateKey key, byte[] data) throws Exception;
boolean verify(PublicKey key, byte[] data, byte[] signature) throws Exception;
}
/**
* Simulated HSM provider (in production, use PKCS#11 or similar)
*/
public static class SimulatedHSM implements HSMProvider {
private final KeyStore keyStore;
public SimulatedHSM() throws Exception {
keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, "hsm-password".toCharArray());
}
public void importKey(String keyId, KeyPair keyPair, X509Certificate cert)
throws Exception {
keyStore.setKeyEntry(
keyId,
keyPair.getPrivate(),
"key-password".toCharArray(),
new X509Certificate[]{cert}
);
}
@Override
public PrivateKey getPrivateKey(String keyId) {
try {
return (PrivateKey) keyStore.getKey(keyId, "key-password".toCharArray());
} catch (Exception e) {
throw new RuntimeException("Failed to get private key", e);
}
}
@Override
public PublicKey getPublicKey(String keyId) {
try {
X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyId);
return cert.getPublicKey();
} catch (Exception e) {
throw new RuntimeException("Failed to get public key", e);
}
}
@Override
public byte[] sign(PrivateKey key, byte[] data) throws Exception {
Signature sig = Signature.getInstance("SHA256withECDSA");
sig.initSign(key);
sig.update(data);
return sig.sign();
}
@Override
public boolean verify(PublicKey key, byte[] data, byte[] signature) throws Exception {
Signature sig = Signature.getInstance("SHA256withECDSA");
sig.initVerify(key);
sig.update(data);
return sig.verify(signature);
}
}
/**
* Sign a COSE message using HSM
*/
public static byte[] signWithHSM(byte[] payload, HSMProvider hsm,
String keyId, AlgorithmID algorithm)
throws Exception {
Sign1Message message = new Sign1Message();
message.SetContent(payload);
message.addAttribute(HeaderKeys.Algorithm, algorithm.AsCBOR(), Attribute.PROTECTED);
message.addAttribute(HeaderKeys.KID, keyId.getBytes(), Attribute.UNPROTECTED);
// Get key from HSM
PrivateKey privateKey = hsm.getPrivateKey(keyId);
// Compute signature in HSM
byte[] signature = hsm.sign(privateKey, message.GetContent());
// Set signature (implementation depends on COSE library)
// This is simplified - real implementation would need to set properly
message.SetSignature(signature);
return message.EncodeToBytes();
}
public static void main(String[] args) throws Exception {
// Initialize HSM
SimulatedHSM hsm = new SimulatedHSM();
// Generate key pair
KeyPair keyPair = COSESign1Example.generateKeyPair();
// Import into HSM (simplified - would need certificate)
hsm.importKey("hsm-key-1", keyPair, null);
System.out.println("HSM initialized with key: hsm-key-1");
// Create payload
String payload = "Data signed by HSM";
byte[] payloadBytes = payload.getBytes("UTF-8");
// Sign using HSM
byte[] signed = signWithHSM(
payloadBytes,
hsm,
"hsm-key-1",
AlgorithmID.ECDSA_256
);
System.out.println("COSE message signed in HSM, size: " + signed.length);
// Verify using HSM (or regular verification)
PublicKey publicKey = hsm.getPublicKey("hsm-key-1");
byte[] valid = COSESign1Example.verifyMessage(signed, publicKey);
System.out.println("Verified payload: " + new String(valid, "UTF-8"));
}
}
COSE Header Parameters
| Key | Name | Description |
|---|---|---|
| 1 | Algorithm | Cryptographic algorithm used |
| 2 | Critical | Headers that must be understood |
| 3 | Content Type | Type of the payload |
| 4 | Key ID | Identifier for the key |
| 5 | IV | Initialization Vector |
| 6 | Partial IV | Partial IV for counter mode |
| 7 | Counter Signature | Additional signature |
COSE Algorithm IDs
| Algorithm | Value | Description |
|---|---|---|
| AES-GCM-128 | 1 | AES-GCM with 128-bit key |
| AES-GCM-192 | 2 | AES-GCM with 192-bit key |
| AES-GCM-256 | 3 | AES-GCM with 256-bit key |
| HMAC-SHA-256 | 5 | HMAC with SHA-256 |
| HMAC-SHA-384 | 6 | HMAC with SHA-384 |
| HMAC-SHA-512 | 7 | HMAC with SHA-512 |
| ECDSA-256 | -7 | ECDSA with SHA-256 on P-256 |
| ECDSA-384 | -35 | ECDSA with SHA-384 on P-384 |
| ECDSA-512 | -36 | ECDSA with SHA-512 on P-521 |
| EdDSA | -8 | Ed25519 and Ed448 |
| RSA-PSS | -37 | RSASSA-PSS |
Best Practices
- Algorithm Selection: Choose algorithms appropriate for your security requirements and constraints.
- Key Management: Use Key IDs to identify keys without revealing the keys themselves.
- Protected vs Unprotected: Use protected headers for any data that must be authenticated (including algorithm selection).
- Replay Prevention: Include nonces or timestamps in the payload or headers.
- CBOR Encoding: Use deterministic CBOR encoding for reproducible signatures.
- Size Optimization: For constrained devices, use short key IDs and omit optional headers.
Performance Comparison
| Operation | COSE Size | JOSE Size | Savings |
|---|---|---|---|
| Single Signature | ~200 bytes | ~400 bytes | 50% |
| Encryption | ~250 bytes | ~500 bytes | 50% |
| CWT vs JWT | ~150 bytes | ~300 bytes | 50% |
| Multi-Signature | ~100 bytes/signer | ~200 bytes/signer | 50% |
Conclusion
COSE represents a significant advancement in compact security for modern applications. By leveraging CBOR's efficiency and providing a flexible, standards-based security layer, COSE enables:
- IoT and Embedded Systems: Security within tight memory and bandwidth constraints
- Efficient APIs: Smaller tokens and encrypted payloads
- WebAuthn/FIDO2: Foundation for modern authentication
- Constrained Environments: Ideal for sensors, smart cards, and mobile devices
The Java implementations provided demonstrate the full spectrum of COSE capabilities:
- Signing for authenticity and integrity
- Encryption for confidentiality
- MAC for symmetric authentication
- Multi-party operations for complex scenarios
- CWT for token-based authentication
- HSM integration for hardware security
For Java developers building the next generation of efficient, secure applications, COSE offers the perfect balance of security, efficiency, and standards compliance. As IoT and mobile computing continue to grow, COSE's importance will only increase, making it an essential tool in the modern Java developer's cryptographic toolkit.
Java Programming Intermediate Topics – Modifiers, Loops, Math, Methods & Projects (Related to Java Programming)
Access Modifiers in Java:
Access modifiers control how classes, variables, and methods are accessed from different parts of a program. Java provides four main access levels—public, private, protected, and default—which help protect data and control visibility in object-oriented programming.
Read more: https://macronepal.com/blog/access-modifiers-in-java-a-complete-guide/
Static Variables in Java:
Static variables belong to the class rather than individual objects. They are shared among all instances of the class and are useful for storing values that remain common across multiple objects.
Read more: https://macronepal.com/blog/static-variables-in-java-a-complete-guide/
Method Parameters in Java:
Method parameters allow values to be passed into methods so that operations can be performed using supplied data. They help make methods flexible and reusable in different parts of a program.
Read more: https://macronepal.com/blog/method-parameters-in-java-a-complete-guide/
Random Numbers in Java:
This topic explains how to generate random numbers in Java for tasks such as simulations, games, and random selections. Random numbers help create unpredictable results in programs.
Read more: https://macronepal.com/blog/random-numbers-in-java-a-complete-guide/
Math Class in Java:
The Math class provides built-in methods for performing mathematical calculations such as powers, square roots, rounding, and other advanced calculations used in Java programs.
Read more: https://macronepal.com/blog/math-class-in-java-a-complete-guide/
Boolean Operations in Java:
Boolean operations use true and false values to perform logical comparisons. They are commonly used in conditions and decision-making statements to control program flow.
Read more: https://macronepal.com/blog/boolean-operations-in-java-a-complete-guide/
Nested Loops in Java:
Nested loops are loops placed inside other loops to perform repeated operations within repeated tasks. They are useful for pattern printing, tables, and working with multi-level data.
Read more: https://macronepal.com/blog/nested-loops-in-java-a-complete-guide/
Do-While Loop in Java:
The do-while loop allows a block of code to run at least once before checking the condition. It is useful when the program must execute a task before verifying whether it should continue.
Read more: https://macronepal.com/blog/do-while-loop-in-java-a-complete-guide/
Simple Calculator Project in Java:
This project demonstrates how to create a basic calculator program using Java. It combines input handling, arithmetic operations, and conditional logic to perform simple mathematical calculations.
Read more: https://macronepal.com/blog/simple-calculator-project-in-java/