2

我目前正在开发用于连接到 Google Cloud IoT Core 的 Android Things 程序。我曾经对 Google 提供的 maven 代码进行采样,并针对 Gradle 进行了修改(包含所有导入和内容)。在进行各种检查后,每当我尝试在运行 Android Things 的 Raspberry Pi3 上运行程序时,它都会一直出现此错误

W/System.err: java.io.FileNotFoundException: com/example/adityaprakash/test/rsa_private.pem (No such file or directory)

告诉我我应该用于 JWT 的私钥文件不存在,尽管它确实存在,并且我已经给出了 pem 文件的路径。这是我的 java 代码

package com.example.adityaprakash.test;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        Log.i("#########","######");
        MqttExample mqtt = new MqttExample();
        try {
            mqtt.Start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

MqttExample.java

package com.example.adityaprakash.test;
// [END cloudiotcore_mqtt_imports]
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

import org.joda.time.DateTime;

import java.io.BufferedReader;
import java.io.FileReader;

import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;

import android.util.Base64;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class MqttExample {

    // [START cloudiotcore_mqtt_createjwt]
    /** Create a Cloud IoT Core JWT for the given project id, signed with the given RSA key. */
    public static String createJwtRsa(String projectId, String privateKeyFile) throws Exception {
        DateTime now = new DateTime();

        String strKeyPEM = "";
        BufferedReader br = new BufferedReader(new FileReader(privateKeyFile));
        String line;
        while ((line = br.readLine()) != null) {
            strKeyPEM += line + "\n";
        }
        br.close();
        // Create a JWT to authenticate this device. The device will be disconnected after the token
        // expires, and will have to reconnect with a new token. The audience field should always be set
        // to the GCP project id.
        JwtBuilder jwtBuilder =
                Jwts.builder()
                        .setIssuedAt(now.toDate())
                        .setExpiration(now.plusMinutes(20).toDate())
                        .setAudience(projectId);
        String privateKeyPEM = strKeyPEM;
        privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\n", "");
        privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", "");
        byte[] encoded = Base64.decode(privateKeyPEM,Base64.DEFAULT);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);
        KeyFactory kf = KeyFactory.getInstance("RSA");

        return jwtBuilder.signWith(SignatureAlgorithm.RS256, kf.generatePrivate(spec)).compact();
    }


    /** Parse arguments, configure MQTT, and publish messages. */
    public void Start() throws Exception {
        // [START cloudiotcore_mqtt_configuremqtt]
        MqttExampleOptions options = MqttExampleOptions.values();
        if (options == null) {
            // Could not parse.
            System.exit(1);
        }
        // Build the connection string for Google's Cloud IoT Core MQTT server. Only SSL
        // connections are accepted. For server authentication, the JVM's root certificates
        // are used.
        final String mqttServerAddress =
                String.format("ssl://%s:%s", options.mqttBridgeHostname, options.mqttBridgePort);

        // Create our MQTT client. The mqttClientId is a unique string that identifies this device. For
        // Google Cloud IoT Core, it must be in the format below.
        final String mqttClientId =
                String.format(
                        "projects/%s/locations/%s/registries/%s/devices/%s",
                        options.projectId, options.cloudRegion, options.registryId, options.deviceId);

        MqttConnectOptions connectOptions = new MqttConnectOptions();
        // Note that the the Google Cloud IoT Core only supports MQTT 3.1.1, and Paho requires that we
        // explictly set this. If you don't set MQTT version, the server will immediately close its
        // connection to your device.
        connectOptions.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);

        // With Google Cloud IoT Core, the username field is ignored, however it must be set for the
        // Paho client library to send the password field. The password field is used to transmit a JWT
        // to authorize the device.
        connectOptions.setUserName("unused");

        System.out.println(options.algorithm);
        if (options.algorithm.equals("RS256")) {
            connectOptions.setPassword(
                    createJwtRsa(options.projectId, options.privateKeyFile).toCharArray());
        }else {
            throw new IllegalArgumentException(
                    "Invalid algorithm " + options.algorithm + ". Should be one of 'RS256' or 'ES256'.");
        }
        // [END cloudiotcore_mqtt_configuremqtt]

        // [START cloudiotcore_mqtt_publish]
        // Create a client, and connect to the Google MQTT bridge.
        MqttClient client = new MqttClient(mqttServerAddress, mqttClientId, new MemoryPersistence());
        try {
            client.connect(connectOptions);

            // Publish to the events or state topic based on the flag.
            String subTopic = options.messageType.equals("event") ? "events" : options.messageType;

            // The MQTT topic that this device will publish telemetry data to. The MQTT topic name is
            // required to be in the format below. Note that this is not the same as the device registry's
            // Cloud Pub/Sub topic.
            String mqttTopic = String.format("/devices/%s/%s", options.deviceId, subTopic);

            // Publish numMessages messages to the MQTT bridge, at a rate of 1 per second.
            for (int i = 1; i <= options.numMessages; ++i) {
                String payload = String.format("%s/%s-payload number-%d", options.registryId, options.deviceId, i);
                System.out.format(
                        "Publishing %s message %d/%d: '%s'\n",
                        options.messageType, i, options.numMessages, payload);

                // Publish "payload" to the MQTT topic. qos=1 means at least once delivery. Cloud IoT Core
                // also supports qos=0 for at most once delivery.
                MqttMessage message = new MqttMessage(payload.getBytes());
                message.setQos(1);
                client.publish(mqttTopic, message);

                if (options.messageType.equals("event")) {
                    // Send telemetry events every second
                    Thread.sleep(1000);
                }
                else {
                    // Note: Update Device state less frequently than with telemetry events
                    Thread.sleep(5000);
                }
            }
        } finally {
            // Disconnect the client and finish the run.
            client.disconnect();
        }
        System.out.println("Finished loop successfully. Goodbye!");
        // [END cloudiotcore_mqtt_publish]
    }

}

和 MqttExampleOptions.java 代码:

package com.example.adityaprakash.test;

public class MqttExampleOptions {
    String projectId;
    String registryId;
    String deviceId;
    String privateKeyFile;
    String algorithm;
    String cloudRegion;
    int numMessages;
    String mqttBridgeHostname;
    short mqttBridgePort;
    String messageType;

    /** Construct an MqttExampleOptions class. */
    public static MqttExampleOptions values() {

        try {

            MqttExampleOptions res = new MqttExampleOptions();

            res.projectId = "_";
            res.registryId = "_";
            res.deviceId = "_";
            res.privateKeyFile = "com/example/adityaprakash/test/rsa_private.pem";
            res.algorithm = "RS256";
            res.cloudRegion = "asia-east1";
            res.numMessages = 100;
            res.mqttBridgeHostname = "mqtt.googleapis.com";
            res.mqttBridgePort = 8883;
            res.messageType = "event";
            return res;

        } catch (Exception e) {
            System.err.println(e.getMessage());
            return null;
        }
    }
}

请任何人都可以解决这个问题。
PS我知道代码看起来很糟糕。我没有Android编程经验,所以请放手。

4

3 回答 3

4

您所遵循的示例不是为 Android 设计的。

res.privateKeyFile = "com/example/adityaprakash/test/rsa_private.pem";

不会涉及到 Android 文件系统上的同一目录。


我在这里写了一个关于如何与 Cloud IoT Core 对话的AndroidThings解释:http: //blog.blundellapps.co.uk/tut-google-cloud-iot-core-mqtt-on-android/

您可以像这样设置通信(您的pem文件进入/raw目录)

// Setup the communication with your Google IoT Core details
        communicator = new IotCoreCommunicator.Builder()
                .withContext(this)
                .withCloudRegion("your-region") // ex: europe-west1
                .withProjectId("your-project-id")   // ex: supercoolproject23236
                .withRegistryId("your-registry-id") // ex: my-devices
                .withDeviceId("a-device-id") // ex: my-test-raspberry-pi
                .withPrivateKeyRawFileId(R.raw.rsa_private)
                .build();

源代码在这里:https ://github.com/blundell/CloudIoTCoreMQTTExample


请注意,以上对于安全环境或测试端到端是否有效已经足够了。但是,如果您想发布生产 IoT 设备,您会考虑将 PEM 嵌入 ROM 并使用私有文件存储访问。https://developer.android.com/training/articles/keystore.html

这方面的一个例子可以在这里找到:https ://github.com/androidthings/sensorhub-cloud-iot

特别是这个类:

https://github.com/androidthings/sensorhub-cloud-iot/blob/e50bde0100fa81818ebbadb54561b3b68ccb64b8/app/src/main/java/com/example/androidthings/sensorhub/cloud/cloudiot/MqttAuthentication.java

然后,您可以在设备上生成和使用 PEM:

  public Certificate getCertificate() {
    KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
    ks.load(null);

    certificate = ks.getCertificate("Cloud IoT Authentication");
    if (certificate == null) {
      Log.w(TAG, "No IoT Auth Certificate found, generating new cert");
      generateAuthenticationKey();
      certificate = ks.getCertificate(keyAlias);
    }
    Log.i(TAG, "loaded certificate: " + keyAlias); 
 }

    private void generateAuthenticationKey() throws GeneralSecurityException {
      KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
      kpg.initialize(new KeyGenParameterSpec.Builder("Cloud IoT Authentication",KeyProperties.PURPOSE_SIGN)
       .setKeySize(2048)
       .setCertificateSubject(new X500Principal("CN=unused"))
       .setDigests(KeyProperties.DIGEST_SHA256)
     .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
       .build());

      kpg.generateKeyPair();
   }
于 2017-11-15T08:09:19.230 回答
2

我很确定您没有正确执行文件 I/O。您的文件"com/example/adityaprakash/test/rsa_private.pem"与设备上的实际文件路径不对应。设备上文件的位置可能与您的项目中不同。您必须确定您的文件在设备上的实际位置。

于 2017-11-14T18:40:56.977 回答
1

在 AndroidThings 上,更容易在 Android 资源中提供身份验证凭据。请参阅我的 WeatherStation 示例分支,了解它是如何工作的。

首先,将私钥文件(例如rsa_private_pkcs8)复制到app/src/main/res/raw/privatekey.txt

接下来,您可以将用于计算 JWT 的密钥加载为:

Context mContext;
int resIdPk = getResources().getIdentifier("privatekey", "raw", getPackageName());    

...

InputStream privateKey = mContext.getResources().openRawResource(resIdPk);
byte[] keyBytes = inputStreamToBytes(privateKey);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("EC");

最后一点,您似乎引用了一个不是 pkcs8 格式的文件,这将导致 Java 出现问题。在 Android (Java) 上打开凭据时,请确保使用打包在 PKCS8 中的密钥。

于 2017-11-14T18:43:55.713 回答