Hiu implementation for milestone-3

Hello Ndhm Team,
we are stuck at milestone -3 getting this error while retriving data as hiu,can any one please let us know what is issue.
@mdubey sir

scenario-
we have registered as hip for milestone-2 and sended data to the phr app and for milestone-3 we have registered as hiu and retriving the data for that same user from phr app to our software but getting the below exception.

java.lang.IllegalArgumentException: Invalid point encoding 0x-6c
at org.bouncycastle.math.ec.ECCurve.decodePoint(ECCurve.java:441)
at com.dpdocter.security.DHKeyExchangeCrypto.loadPublicKey(DHKeyExchangeCrypto.java:379)
at com.dpdocter.security.DHKeyExchangeCrypto.doECDH(DHKeyExchangeCrypto.java:388)
at com.dpdocter.security.DHKeyExchangeCrypto.encrypt(DHKeyExchangeCrypto.java:271)

Hi @Shubham,

Could you please try replacing the methods for decryption part as well with below:

    * */
    // Replacement for ------> getEncodedPublicKey
    public static byte[] getEncodedPublicKeyForProjectEKAHIU(PublicKey key){
        ECPublicKey ecKey = (ECPublicKey)key;
        return ecKey.getEncoded();
    }
    
    // Replacement for ------> loadPublicKey
    private static PublicKey loadPublicKeyForProjectEKAHIU (byte [] data) throws Exception
    {
        KeyFactory ecKeyFac = KeyFactory.getInstance(ALGORITHM, PROVIDER);
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(data);
        PublicKey publicKey = ecKeyFac.generatePublic(x509EncodedKeySpec);
        return publicKey;
    } 

Thank you

Hello sir,
Okay will do the changes and check.

Thanks

Hello sir,
I have tried the above solution by got another issue,please check

java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: unknown tag 28 encountered
at org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi.engineGeneratePublic(Unknown Source)
at org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi.engineGeneratePublic(Unknown Source)
at java.base/java.security.KeyFactory.generatePublic(KeyFactory.java:346)
at com.dpdocter.security.DHKeyExchangeCrypto.loadPublicKeyForProjectEKAHIU(DHKeyExchangeCrypto.java:452)
at com.dpdocter.security.DHKeyExchangeCrypto.doECDH(DHKeyExchangeCrypto.java:388)
at com.dpdocter.security.DHKeyExchangeCrypto.encrypt(DHKeyExchangeCrypto.java:271)

Hello sir,
can you please tell me how to decrypt data ,for milestone -3, as we get encrypted data.
@mdubey sir

Hi @Shubham,

You can look at our open source Java implementation for the same on github. We are using the similar approach as mentioned in the gists.

Hello Sir,
Actually we are getting content as null after decrypting

Hello sir,
we are using the same code which is used for encryption and decryption DHKeyExchangeCrypto.java this file ,but the one you sent i think it’s different and we have to use this file for hiu implementation?

@Shubham were you able to encrypt the data? We have tried many time and failed. We got no helpful from NDHM. Can you please share your Java encryption code?

Hello sir,
java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: Extra data detected in stream
and also we have another doubt ,do we have to replace the loadpublic key method with the below method because after replacing that we are getting above exception.
@mdubey sir

private static PublicKey loadPublicKeyForProjectEKAHIU (byte [] data) throws Exception
{
KeyFactory ecKeyFac = KeyFactory.getInstance(ALGORITHM, PROVIDER);
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(data);
PublicKey publicKey = ecKeyFac.generatePublic(x509EncodedKeySpec);
return publicKey;
}

Hi @bhaskar.dabhi,
below is the link we have used for encryption and decryption process.

We tried same. It is not working for us.

it is working for encryption ,just replace getEncodedPublicKey and loadPublicKey with below methods.
// Replacement for ------> getEncodedPublicKey
public static byte[] getEncodedPublicKeyForProjectEKAHIU(PublicKey key){
ECPublicKey ecKey = (ECPublicKey)key;
return ecKey.getEncoded();
}

    // Replacement for ------> loadPublicKey
    private static PublicKey loadPublicKeyForProjectEKAHIU (byte [] data) throws Exception
    {
        KeyFactory ecKeyFac = KeyFactory.getInstance(ALGORITHM, PROVIDER);
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(data);
        PublicKey publicKey = ecKeyFac.generatePublic(x509EncodedKeySpec);
        return publicKey;
    }

Hello sir,
we are sending data to phr app using
loadPublicKey method while encrypting ,but it is not working while sending the same to our hiu
for sending in our hiu data encryption is working with
loadPublicKeyForProjectEKAHIU method, if we use this method for sending in phr app it is showing below error
java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: Extra data detected in stream
@mdubey sir please check

Hi @Shubham and @bhaskar.dabhi,

Here is the implementation of our sample C# HIP-Service which is working and we use for all the demos and webinars.

You can have a look, this also follows same gist which has been provided to you.

Hi @Shubham, @bhaskar.dabhi,
Please find the below java classes which has been used for encryption and has been working with PHR app successfully. Please have a look and let us know if it helps.

DataEntryFactory class
Method process taken list of entries and key material and returns list of encrypted entries.

@AllArgsConstructor
public class DataEntryFactory {
    public static final String HEALTH_INFORMATION_LINK = "%s/health-information/%s?token=%s";
    public static final String MEDIA_APPLICATION_FHIR_JSON = "application/fhir+json";
    private final Encryptor encryptor;

    public Mono<EncryptedEntries> process(Entries entries, KeyMaterial dataRequestKeyMaterial)
            throws Exception {
        var keyPair = Encryptor.generateKeyPair();
        var randomKey = Encryptor.generateRandomKey();
        List<Entry> processedEntries = new ArrayList<>();
        entries.getCareBundles().forEach(careBundle -> {
            String encryptData = "";
            try {
                encryptData = encryptor.encryptData(dataRequestKeyMaterial, keyPair, careBundle.getContent(), randomKey);
            } catch (Exception exception) {
                exception.printStackTrace();
            }
            if (StringUtils.isNotEmpty(encryptData)) {
                processedEntries.add(componentEntry(encryptData, careBundle.getCareContextReference()));
            }
        });
        var keyStructure = new KeyStructure(LocalDateTime.now(ZoneOffset.UTC),
                dataRequestKeyMaterial.getDhPublicKey().getParameters(),
                Encryptor.getBase64String(Encryptor.getEncodedPublicKeyForProjectEKAHIU(keyPair.getPublic())));
        var keyMaterial = new KeyMaterial(dataRequestKeyMaterial.getCryptoAlg(),
                dataRequestKeyMaterial.getCurve(), keyStructure, randomKey);
        return Mono.just(new EncryptedEntries(processedEntries, keyMaterial));
    }


    private static Entry entryWith(String content, String link, String careContextReference)
    {
        return Entry.builder()
                .content(content)
                .media(MEDIA_APPLICATION_FHIR_JSON)
                .checksum("MD5")
                .link(link)
                .careContextReference(careContextReference)
                .build();
    }

    private static Entry componentEntry(String serializedBundle, String careContextReference)
    {
        return entryWith(serializedBundle, null, careContextReference);
    }
}

Encryptor.java
Method encryptData called by above class which does the actual enncryption.

public class Encryptor {
    private static final Logger logger = LoggerFactory.getLogger(Encryptor.class);
    public static final String ALGORITHM = "ECDH";
    public static final String CURVE = "curve25519";
    public static final String PROVIDER = BouncyCastleProvider.PROVIDER_NAME;

    public String encryptData(KeyMaterial keyMaterial,
                                    KeyPair keyPair,
                                    String content, String randomKey) throws Exception
    {
        String privateKey = getBase64String(Encryptor.getEncodedPrivateKey(keyPair.getPrivate()));
        var nonce = keyMaterial.getNonce();
        byte[] xorOfRandom = xorOfRandom(randomKey, nonce);

        return encrypt(xorOfRandom, privateKey, keyMaterial.getDhPublicKey().getKeyValue(), content);
    }

    private static String encrypt(byte[] xorOfRandom, String senderPrivateKey, String receiverPublicKey, String stringToEncrypt) throws Exception {
        String sharedKey = doECDH(getBytesForBase64String(senderPrivateKey), getBytesForBase64String(receiverPublicKey));
        byte[] iv = Arrays.copyOfRange(xorOfRandom, xorOfRandom.length - 12, xorOfRandom.length);
        byte[] aesKey = generateAesKey(xorOfRandom, sharedKey);

        String encryptedData = "";
        try {
            byte[] stringBytes = stringToEncrypt.getBytes();

            GCMBlockCipher cipher = new GCMBlockCipher(new AESEngine());
            AEADParameters parameters =
                    new AEADParameters(new KeyParameter(aesKey), 128, iv, null);

            cipher.init(true, parameters);
            byte[] plainBytes = new byte[cipher.getOutputSize(stringBytes.length)];
            int retLen = cipher.processBytes
                    (stringBytes, 0, stringBytes.length, plainBytes, 0);
            cipher.doFinal(plainBytes, retLen);

            encryptedData = getBase64String(plainBytes);
        } catch (Exception exception) {
            logger.error(exception.getMessage(), exception);
        }
        return encryptedData;
    }

    public static String generateRandomKey() {
        byte[] salt = new byte[32];
        SecureRandom random = new SecureRandom();
        random.nextBytes(salt);
        return getBase64String(salt);
    }

    public static KeyPair generateKeyPair() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
        Security.addProvider(new BouncyCastleProvider());
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER);
        X9ECParameters ecParameters = CustomNamedCurves.getByName(CURVE);
        ECParameterSpec ecSpec=new ECParameterSpec(ecParameters.getCurve(), ecParameters.getG(),
                ecParameters.getN(), ecParameters.getH(), ecParameters.getSeed());

        keyPairGenerator.initialize(ecSpec, new SecureRandom());
        return keyPairGenerator.generateKeyPair();
    }

    private static PrivateKey loadPrivateKey (byte [] data) throws Exception
    {
        X9ECParameters ecP = CustomNamedCurves.getByName(CURVE);
        ECParameterSpec params=new ECParameterSpec(ecP.getCurve(), ecP.getG(),
                ecP.getN(), ecP.getH(), ecP.getSeed());
        ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(data), params);
        KeyFactory kf = KeyFactory.getInstance(ALGORITHM, PROVIDER);
        return kf.generatePrivate(privateKeySpec);
    }

    private static PublicKey loadPublicKey (byte [] data) throws Exception
    {
        Security.addProvider(new BouncyCastleProvider());
        X9ECParameters ecP = CustomNamedCurves.getByName(CURVE);
        ECParameterSpec ecNamedCurveParameterSpec = new ECParameterSpec(ecP.getCurve(), ecP.getG(),
                ecP.getN(), ecP.getH(), ecP.getSeed());

        return KeyFactory.getInstance(ALGORITHM, PROVIDER)
                .generatePublic(new ECPublicKeySpec(ecNamedCurveParameterSpec.getCurve().decodePoint(data),
                        ecNamedCurveParameterSpec));
    }

    private static String doECDH (byte[] dataPrv, byte[] dataPub) throws Exception
    {
        KeyAgreement ka = KeyAgreement.getInstance(ALGORITHM, PROVIDER);
        ka.init(loadPrivateKey(dataPrv));
        ka.doPhase(loadPublicKey(dataPub), true);
        byte [] secret = ka.generateSecret();
        return getBase64String(secret);
    }

    private static byte [] xorOfRandom(String randomKeySender, String randomKeyReceiver)
    {
        byte[] randomSender = getBytesForBase64String(randomKeySender);
        byte[] randomReceiver = getBytesForBase64String(randomKeyReceiver);

        byte[] out = new byte[randomSender.length];
        for (int i = 0; i < randomSender.length; i++) {
            out[i] = (byte) (randomSender[i] ^ randomReceiver[i%randomReceiver.length]);
        }
        return out;
    }

    private static byte [] generateAesKey(byte[] xorOfRandoms, String sharedKey ){
        byte[] salt = Arrays.copyOfRange(xorOfRandoms, 0, 20);
        HKDFBytesGenerator hkdfBytesGenerator = new HKDFBytesGenerator(new SHA256Digest());
        HKDFParameters hkdfParameters = new HKDFParameters(getBytesForBase64String(sharedKey), salt, null);
        hkdfBytesGenerator.init(hkdfParameters);
        byte[] aesKey = new byte[32];
        hkdfBytesGenerator.generateBytes(aesKey, 0, 32);
        return aesKey;
    }

    public static String getBase64String(byte[] value){

        return new String(org.bouncycastle.util.encoders.Base64.encode(value));
    }

    public static byte[] getBytesForBase64String(String value){
        return org.bouncycastle.util.encoders.Base64.decode(value);
    }

    public static byte[] getEncodedPublicKeyForProjectEKAHIU(PublicKey key){
        ECPublicKey ecKey = (ECPublicKey)key;
        return ecKey.getEncoded();
    }

    public static byte [] getEncodedPrivateKey(PrivateKey key) throws Exception
    {
        ECPrivateKey ecKey = (ECPrivateKey)key;
        return ecKey.getD().toByteArray();
    }
}

Def for EncryptedEntries and Entry. Annotations are from Lombok.

@Value
@Builder
@AllArgsConstructor
public class EncryptedEntries {
    List<Entry> entries;
    KeyMaterial keyMaterial;

    @Value
    @Builder
    public static class Entry {
        String content;
        String media;
        String checksum;
        String link;
        String careContextReference;
    }
}

Def for KeyMaterial and KeyStructure. Annotations are from Lombok.

@Value
@Builder
@AllArgsConstructor
public class KeyMaterial {
    String cryptoAlg;
    String curve;
    KeyStructure dhPublicKey;
    String nonce;

    @Value
    @Builder
    @AllArgsConstructor
    public static class KeyStructure {
        LocalDateTime expiry;
        String parameters;
        String keyValue;
    }
}

Def for Entries. Annotations are from Lombok.

@Value
@Builder
public class Entries {
    List<CareBundle> careBundles;

    @Value
    @Builder
    public class CareBundle {
        String careContextReference;
        String content;
    }
}

You can ignore the Mono part and just return the EncryptedEntries. This is present because we are using reactive java stack. In the last line replace

return Mono.just(new EncryptedEntries(processedEntries, keyMaterial))

With

return new EncryptedEntries(processedEntries, keyMaterial)

@mdubey Thanks for the helper. We will check and will get back to you

Sir,but what about decrypting the data ?

Hi @Shubham,

Were you able to check with the Link given to our open source HIU implementation? That is what is used for the PHR app.

In our case we data is encrypting properly and also visible in the phr app,but when we send that same data to our hiu then it is showing
java.lang.IllegalArgumentException: Invalid point encoding 0x-6c
at org.bouncycastle.math.ec.ECCurve.decodePoint(ECCurve.java:441)
at com.dpdocter.security.DHKeyExchangeCrypto.loadPublicKey(DHKeyExchangeCrypto.java:379)
at com.dpdocter.security.DHKeyExchangeCrypto.doECDH(DHKeyExchangeCrypto.java:388)
at com.dpdocter.security.DHKeyExchangeCrypto.encrypt(DHKeyExchangeCrypto.java:271)