android N中启用了安全“加密”提供程序

针对Android N的更新,Google宣布弃用SHA1PRNG算法,因其密码强度不足。文章详细介绍了该算法的问题及替代方案,并提供了一个示例帮助开发者过渡到更安全的密钥派生方法。

针对应用和游戏开发者的最新Android和Google Play新闻。
Android N中弃用了安全“加密”提供程序
2016年6月9日
软件工程师Sergio Giro发布


如果您的Android应用程序使用来自加密提供程序的SHA1PRNG算法派生密钥,则必须开始使用真正的密钥派生函数,并可能重新加密数据。


Java加密体系结构允许开发人员使用以下调用创建类的实例,如密码或伪随机数生成器:
SomeClass.getInstance("SomeAlgorithm", "SomeProvider");
或者干脆:
SomeClass.getInstance("SomeAlgorithm");
例如,
Cipher.getInstance(“AES/CBC/PKCS5PADDING”);
SecureRandom.getInstance(“SHA1PRNG”);
在Android上,我们不建议指定提供者。一般来说,只有在提供程序包含在应用程序中或者应用程序能够处理可能的ProviderNotFoundException时,才可以调用指定提供程序的Java Cryptography Extension(JCE)API。


不幸的是,许多应用程序依赖于现在删除的“加密”提供商提供的密钥派生的反模式。


该提供者仅为SecureRandom实例提供了算法“SHA1PRNG”的实现。问题是SHA1PRNG算法不具有密码强度。对于对细节感兴趣的读者,由Yongge Want和Tony Nicol撰写的 基于统计距离的伪随机序列测试和PHP和Debian OpenSSL 8.1节的实验表明,以二进制形式考虑的“随机”序列偏向于返回0,并且偏差根据种子而恶化。


因此, 在Android N中,我们不赞成SHA1PRNG算法和Crypto提供者的实现。我们之前已经介绍了几年前使用SecureRandom进行密钥派生时 使用加密技术安全存储凭证的问题。但是,鉴于其持续使用,我们将在此重新审视。


此提供程序的一个常见但不正确的用法是通过将密码用作种子来派生加密密钥。SHA1PRNG的实现有一个错误,如果在获取输出之前调用了setSeed(),则会导致确定性。这个bug被用来通过提供一个密码作为种子来获得密钥,然后使用密钥的“随机”输出字节(在这个句子中的“随机”意味着“可预测的和密码学上的弱”)。然后可以使用这样的密钥来加密和解密数据。


在下文中,我们将解释如何正确导出密钥,以及如何解密使用不安全密钥加密的数据。还有一个 完整的示例,其中包括使用已弃用的SHA1PRNG功能的帮助程序类,其唯一目的是解密否则将不可用的数据。


密钥可以通过以下方式导出:


如果你从磁盘读取AES密钥,只需存储实际的密钥,不要通过这个奇怪的舞蹈。您可以通过执行字节获得AES用法的SecretKey:
SecretKey key = new SecretKeySpec(keyBytes, "AES");
如果您使用密码来派生密钥, 请遵循Nikolay Elenkov出色的教程,并注意一条好的经验法则,即盐的大小应与关键输出的大小相同。它看起来像这样:
   
/ *用户输入密码:* / String password = “password” ;  
     
   / *将这些内容存储在用于派生密钥的磁盘上:* / 
int iterationCount = 1000 ; int saltLength = 32 ; 
//字节; 应的尺寸相同为输出(256 / 8 = 32 )
INT keyLength = 256 ; 
// AES-256的256位,AES-128的128位等 
  byte [] salt ;
 //应该是saltLength    
      
   / *首次创建密钥时,用以下方法获得一个salt:* /
 SecureRandom random = new SecureRandom (); 
byte [] salt = new byte [ saltLength ];   
   random.nextBytes (salt );  
       
   / *使用此推导从密码密钥:* / 
KeySpec keySpec = new PBEKeySpec (password.toCharArray (),salt,  
              iterationCount ,keyLength ); 
SecretKeyFactory keyFactory = SecretKeyFactory 。getInstance (“PBKDF2WithHmacSHA1” ); byte [] keyBytes = keyFactory.generateSecret (keySpec) .getEncoded (); 
SecretKey key = new SecretKeySpec (keyBytes ,“AES” );                      
而已。你不应该需要任何其他东西。

为了简化转换数据,我们介绍了使用不安全密钥加密数据的开发人员的情况,该密钥是每次都从密码派生的。您可以使用辅助类 InsecureSHA1PRNGKeyDerivator中 的 示例应用程序派生密钥。

 
private static SecretKey deriveKeyInsecurely (String password,int 
 keySizeInBytes ){ byte [] passwordBytes = password.getBytes (StandardCharsets 。US_ASCII ); 

return new SecretKeySpec (InsecureSHA1PRNGKeyDerivator 。deriveInsecureKey (  

                     passwordBytes ,keySizeInBytes ),“AES” ); }   
   
然后,您可以使用安全派生的密钥重新对您的数据进行加密,如上所述,过上幸福的生活。


注1:作为保持应用程序正常工作的临时措施,我们决定仍然为面向SDK版本23的应用程序创建实例,该版本是Marshmallow的SDK版本或更少。请不要依赖Android SDK中的Crypto提供程序,我们的计划是在将来彻底删除它。


注2:由于系统的许多部分都假定存在SHA1PRNG算法,因此当请求SHA1PRNG实例并且未指定提供者时,我们将返回一个OpenSSLRandom实例,该实例是从OpenSSL派生的随机数的强大来源。
<think>我们参考了用户提供的引用内容,特别是引用[1]和引用[2]中提到的使用Socket进行网口通信的方法。用户的需求是通过网口下载数据,这实际上是通过网络接口(以太网或Wi-Fi)进行数据传输。在Android中,我们可以使用Socket编程来实现。根据引用[1]:“网口通讯的原理和串口通讯差不多,只是他们初始化的时候不一样,后面数据的读取可以说是一模一样。就是要让外设与你的android程序在同一个局域网内就可以进行通讯了,主要用到的就是socket。初始化的时候用socket对象的getOutputStream()和getInputStream()方法就可以了。”引用[2]也给出了使用Socket发送消息的示例。因此,我们可以通过以下步骤实现网口下载数据:1.在同一个局域网内,知道目标设备的IP地址和端口号。2.创建一个Socket连接。3.通过Socket的输入输出流进行数据的读写。具体步骤:1.添加网络权限:在AndroidManifest.xml中添加:```xml<uses-permissionandroid:name="android.permission.INTERNET"/>```2.创建Socket连接,并获取输入输出流。3.发送请求(如果需要)并读取数据(下载)。4.关闭连接。注意:由于网络操作不能在主线程中进行,我们需要在子线程中执行。代码示例(下载数据):假设我们有一个服务器,它监听某个端口,我们可以向它发送一个请求,然后从输入流中读取数据(例如文件或文本)。以下是一个简单的示例,通过Socket下载文本数据:```javanewThread(()->{Socketsocket=null;try{Stringhost="192.168.1.100";//目标设备的IPintport=8888;//目标设备的端口socket=newSocket(host,port);//发送请求(如果需要)OutputStreamout=socket.getOutputStream();Stringrequest="GET/data.txtHTTP/1.1\r\nHost:192.168.1.100\r\n\r\n";//模拟HTTP请求,具体根据你的协议来定out.write(request.getBytes());out.flush();//读取响应(下载数据)InputStreamin=socket.getInputStream();BufferedReaderreader=newBufferedReader(newInputStreamReader(in));StringBuilderresponse=newStringBuilder();Stringline;while((line=reader.readLine())!=null){response.append(line).append('\n');}//处理数据,比如显示在UI上finalStringresult=response.toString();runOnUiThread(()->{textView.setText(result);//假设有一个TextView显示结果});}catch(IOExceptione){e.printStackTrace();}finally{try{if(socket!=null){socket.close();}}catch(IOExceptione){e.printStackTrace();}}}).start();```注意:上面的例子是一个简单的文本下载,并且模拟了一个HTTP请求。实际应用中,你需要根据服务端的协议来构造请求。另外,如果下载的是文件(如图片、视频等),则应该使用字节流,并且可能需要处理二进制数据,例如:```java//读取字节流InputStreamin=socket.getInputStream();FileOutputStreamfileOut=newFileOutputStream(newFile(getExternalFilesDir(null),"downloaded_file.dat"));byte[]buffer=newbyte[1024];intlen;while((len=in.read(buffer))!=-1){fileOut.write(buffer,0,len);}fileOut.close();```但是,如果服务端提供的是标准的HTTP服务,我们通常使用HttpURLConnection或OkHttp(如之前的回答)会更方便,因为它们处理了HTTP协议的各种细节。而使用Socket直接通信通常用于自定义协议,或者非HTTP的TCP通信。因此,如果下载数据是通过HTTP协议,建议使用OkHttp或HttpURLConnection(参考之前的回答)。如果是通过自定义的TCP协议,则使用Socket。根据引用[3]提到的Retrofit,它实际上是一个基于OkHttp的RESTful客户端,适用于HTTPAPI的调用。所以,如果是HTTP接口,Retrofit也是一个很好的选择。总结:-使用HTTP协议:OkHttp/HttpURLConnection/Retrofit-使用自定义TCP协议:Socket用户的问题是通过网口下载数据,没有明确协议,所以两种方式都可以考虑。但根据引用[1][2]的内容,用户可能更倾向于使用Socket(因为提到了网口通讯)。另外,引用[4]和[5]提到了Android以太网网口注册和USB虚拟网口的内容,但这些属于系统底层配置,普通应用开发一般不需要关心,除非是系统开发或需要特殊网络配置。因此,针对应用开发,我们提供Socket和HTTP两种方式。下面补充使用OkHttp下载文件的示例(更简单,且推荐用于HTTP):1.添加依赖:implementation'com.squareup.okhttp3:okhttp:4.9.3'2.代码:```javaOkHttpClientclient=newOkHttpClient();Requestrequest=newRequest.Builder().url("http://192.168.1.100:8888/data.txt")//下载URL.build();client.newCall(request).enqueue(newCallback(){@OverridepublicvoidonFailure(Callcall,IOExceptione){e.printStackTrace();}@OverridepublicvoidonResponse(Callcall,Responseresponse)throwsIOException{if(response.isSuccessful()){//对于文本数据Stringresult=response.body().string();//或者下载文件InputStreamin=response.body().byteStream();//然后写入文件...}}});```最后,根据用户的需求(网口下载数据)和引用内容,我们更倾向于使用Socket,因为引用[1][2]明确提到了网口通讯使用Socket。但实际开发中,如果服务端是HTTP服务器,使用OkHttp更便捷。请用户根据实际协议选择合适的方法。注意:Android9(Pie)及以上版本默认要求使用加密连接(HTTPS),如果使用HTTP明文请求,需要在网络安全配置中允许明文传输。但对于局域网内的非HTTP服务(如自定义Socket)则没有此限制。相关引用:[^1]提到了使用Socket进行网口通讯。[^2]展示了Socket发送数据的方法。[^3]展示了Retrofit的使用(适用于HTTPAPI)。[^4][^5]涉及底层网络接口,与当前问题关联不大。因此,我们重点使用Socket方式实现网口数据下载。如果用户需要更详细的Socket下载文件的例子,我们可以提供:以下是一个通过Socket下载文件并保存到本地的完整示例(假设服务端发送的是原始文件数据,没有自定义协议头):```javanewThread(()->{Socketsocket=null;FileOutputStreamfileOut=null;try{Stringhost="192.168.1.100";intport=8888;socket=newSocket(host,port);//发送请求(根据服务端要求,可能不需要发送,或者发送一个文件名)OutputStreamout=socket.getOutputStream();Stringrequest="download:myfile.jpg";//假设服务端根据这个字符串提供文件out.write(request.getBytes());out.flush();//读取数据并写入文件InputStreamin=socket.getInputStream();//保存到应用的外部存储私有目录Filefile=newFile(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),"downloaded_file.jpg");fileOut=newFileOutputStream(file);byte[]buffer=newbyte[1024];intbytesRead;while((bytesRead=in.read(buffer))!=-1){fileOut.write(buffer,0,bytesRead);}//下载完成runOnUiThread(()->Toast.makeText(MainActivity.this,"下载完成",Toast.LENGTH_SHORT).show());}catch(IOExceptione){e.printStackTrace();}finally{try{if(fileOut!=null)fileOut.close();if(socket!=null)socket.close();}catch(IOExceptione){e.printStackTrace();}}}).start();```注意:以上代码仅为示例,实际应用中需要处理各种异常,以及根据服务端的协议进行请求和响应的解析。另外,如果下载大文件,可能需要显示进度,可以在循环读取时计算已下载的字节数并更新UI。总结:Android中使用网口(网络接口)下载数据,核心是网络通信。根据协议选择Socket(自定义TCP)或HTTP客户端(如OkHttp)实现。根据引用内容,我们主要展示了Socket的方式,因为引用[1][2]直接提到了网口通讯使用Socket,但同时也提供了HTTP的备选方案。请根据实际情况选择。</think>### Android 通过网口下载数据的方法 在 Android 中通过网口(以太网接口)下载数据,核心是通过 **Socket 编程**建立 TCP/IP 连接。以下是详细实现方法: #### 一、基础步骤 1. **添加网络权限**(必需) ```xml <uses-permission android:name="android.permission.INTERNET" /> ``` 2. **建立 Socket 连接** 参考引用[1][2]的方法: ```java // 目标设备IP和端口 String host = "192.168.1.100"; // 替换为目标设备IP int port = 8080; // 替换为目标端口 try { // 创建Socket连接 Socket socket = new Socket(host, port); // 获取输入流接收数据 InputStream inputStream = socket.getInputStream(); // 获取输出流发送请求(可选) OutputStream outputStream = socket.getOutputStream(); // TODO: 数据传输逻辑 } catch (IOException e) { e.printStackTrace(); } ``` #### 二、完整下载流程 ```java new Thread(() -> { Socket socket = null; FileOutputStream fileOut = null; try { // 1. 建立连接 socket = new Socket("192.168.1.100", 8080); InputStream in = socket.getInputStream(); // 2. 创建本地存储文件 File downloadFile = new File(getExternalFilesDir(null), "downloaded_file.bin"); fileOut = new FileOutputStream(downloadFile); // 3. 下载数据(带进度) byte[] buffer = new byte[4096]; int bytesRead; long totalBytes = 0; while ((bytesRead = in.read(buffer)) != -1) { fileOut.write(buffer, 0, bytesRead); totalBytes += bytesRead; // 更新UI进度(示例) runOnUiThread(() -> progressBar.setProgress((int) (totalBytes * 100 / FILE_SIZE))); } // 4. 完成处理 runOnUiThread(() -> Toast.makeText(this, "下载完成", Toast.LENGTH_SHORT).show()); } catch (IOException e) { e.printStackTrace(); } finally { // 5. 关闭资源 try { if (fileOut != null) fileOut.close(); if (socket != null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } }).start(); ``` #### 三、关键配置说明 1. **网口激活** 系统级配置参考引用[4],确保以太网接口已启用: ```java // 检查以太网状态 ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); Network ethernetNet = cm.getNetworkForType(ConnectivityManager.TYPE_ETHERNET); if (ethernetNet != null) { // 网口已激活 } ``` 2. **特殊场景处理** - **USB 虚拟网口**:参考引用[5],需先识别 USB 设备权限 - **协议封装**:复杂协议建议使用 Protobuf(参考引用[2]) #### 四、优化建议 1. **使用线程池**:避免频繁创建线程 ```java ExecutorService executor = Executors.newFixedThreadPool(4); executor.execute(() -> { /* 下载任务 */ }); ``` 2. **断点续传**:记录已下载位置 ```java long startPosition = getDownloadedSize(); // 获取已下载量 if (startPosition > 0) { // 发送续传请求: "Range: bytes=" + startPosition + "-" } ``` 3. **超时设置** ```java socket.setSoTimeout(30000); // 30秒超时 ``` > **注意事项**: > - 所有网络操作必须在子线程执行 > - Android 9+ 默认禁用明文传输,局域网内需添加网络安全配置 > - 大文件下载建议分块传输 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值