Java中通过UDP加密语音聊天的响亮噪声

时间:2014-05-21 12:39:37

标签: java encryption udp chat voice

我们必须通过UDP构建加密/解密的语音聊天。聊天工作没有加密,但当我添加AES代码加密时,我听到非常大的噪音,这是连续的周期性蜂鸣声信号,但同时我也听到解密的对话,这很好。我需要消除这种噪音。

我们将非常感谢您的帮助。谢谢

发送

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

import javax.sound.sampled.*;

public class MicPlayer {

private static final String IP_TO_STREAM_TO   = "localhost" ;
private static final int PORT_TO_STREAM_TO     = 1234 ;

/** Creates a new instance of MicPlayer */
public MicPlayer() {

}

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
Mixer.Info minfo[] = AudioSystem.getMixerInfo() ;
for( int i = 0 ; i < minfo.length ; i++ )
{
 System.out.println( minfo[i] ) ;    
}


if (AudioSystem.isLineSupported(Port.Info.MICROPHONE)) {
try {


  DataLine.Info dataLineInfo = new DataLine.Info( TargetDataLine.class , getAudioFormat() ) ;
  TargetDataLine targetDataLine = (TargetDataLine)AudioSystem.getLine( dataLineInfo  ) ;
  targetDataLine.open( getAudioFormat() );
  targetDataLine.start();
  byte tempBuffer[] = new byte[8192] ;

  while( true )
  {
  targetDataLine.read( tempBuffer , 0 , tempBuffer.length );
  byte[] encrypt = AES.encrypt(tempBuffer);
  sendThruUDP(encrypt) ;

  }

}
catch(Exception e )
{
System.out.println(" not correct " ) ;
System.exit(0) ;
}
}



}


public static AudioFormat getAudioFormat(){
float sampleRate = 8000.0F;
//8000,11025,16000,22050,44100
int sampleSizeInBits = 16;
//8,16
int channels = 1;
//1,2
boolean signed = true;
//true,false
boolean bigEndian = false;
//true,false
return new AudioFormat( sampleRate, sampleSizeInBits, channels, signed, bigEndian );
}


public static void sendThruUDP( byte soundpacket[] )
{
   try
   {
   DatagramSocket sock = new DatagramSocket() ; 
   sock.send( new DatagramPacket( soundpacket , soundpacket.length , InetAddress.getByName( IP_TO_STREAM_TO ) , PORT_TO_STREAM_TO ) ) ; 
   sock.close() ;
   }
   catch( Exception e )
   {
   e.printStackTrace() ;
   System.out.println(" Unable to send soundpacket using UDP " ) ;   
   }

}



}

接收

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;

public class RadioReceiver extends Thread {

private static final String IP_TO_STREAM_TO   = "localhost" ;
private static final int PORT_TO_STREAM_TO     = 1234;

/** Creates a new instance of RadioReceiver */
public RadioReceiver() {
}

public void run()
{
    byte b[] = null ;
    while( true )
    {
       b = receiveThruUDP() ; 
       toSpeaker( b ) ;
    }        
}

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {

RadioReceiver r = new RadioReceiver() ;
r.start() ;

}


public static byte[] receiveThruUDP()
{
   try
   {
   DatagramSocket sock = new DatagramSocket(PORT_TO_STREAM_TO) ; 
   byte soundpacket[] = new byte[8192] ;
   DatagramPacket datagram = new DatagramPacket( soundpacket , soundpacket.length , InetAddress.getByName( IP_TO_STREAM_TO ) , PORT_TO_STREAM_TO ) ;
   sock.receive( datagram ) ; 
   sock.close() ;
   return AES.decrypt(datagram.getData()); // soundpacket ;
   }
   catch( Exception e )
   {
   System.out.println(" Unable to send soundpacket using UDP " ) ;   
   return null ;
   } 

}


 public static void toSpeaker( byte soundbytes[] )
 {

  try{  
  DataLine.Info dataLineInfo = new DataLine.Info( SourceDataLine.class , getAudioFormat() ) ;
  SourceDataLine sourceDataLine = (SourceDataLine)AudioSystem.getLine( dataLineInfo );
  sourceDataLine.open( getAudioFormat() ) ;
  sourceDataLine.start();

  sourceDataLine.write( soundbytes , 0, soundbytes.length );
  sourceDataLine.drain() ;
  sourceDataLine.close() ;
  }
  catch(Exception e )
  {
  System.out.println("not working in speakers " ) ;
  }

}


public static AudioFormat getAudioFormat()
{
float sampleRate = 44100.0F;
//8000,11025,16000,22050,44100
int sampleSizeInBits = 16;
//8,16
int channels = 1;
//1,2
boolean signed = true;
//true,false
boolean bigEndian = false;
//true,false
return new AudioFormat( sampleRate, sampleSizeInBits, channels, signed, bigEndian );
}



}

AES

import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
static String IV = "AAAAAAAAAAAAAAAA";
static String encryptionKey = "0123456789abcdef";

public static byte[] encrypt(byte[] inputcum) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
cipher.init(Cipher.ENCRYPT_MODE, key,new IvParameterSpec(IV.getBytes("UTF-8")));
return cipher.doFinal(inputcum);
}

public static byte[] decrypt(byte[] cipherSound) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
cipher.init(Cipher.DECRYPT_MODE, key,new IvParameterSpec(IV.getBytes("UTF-8")));
return cipher.doFinal(cipherSound);
}
}

2 个答案:

答案 0 :(得分:1)

问题与UDP或加密无关。每次调用TargetDataLine.read(byte[])只会填充数组的一部分,其余部分会填充前一次调用的剩余部分,但每次都在加密并发送整个数组。

TargetDataLine.read(byte[])的调用与InputStream.read(byte[])类似 - 它返回传输到字节数组中的实际字节数。不得忽略此值。

对于最小工作流程,应按照以下准则修改代码:

发送时:

while( true ) {
    int read = targetDataLine.read( tempBuffer , 0 , tempBuffer.length );
    byte[] encrypt = AES.encrypt(tempBuffer, 0, read);
    sendThruUDP(encrypt) ;
}

加密时(注意填充更改为PKCS5Padding以允许输入长度不是AES块大小的倍数):

public static byte[] encrypt(byte[] plainData, int offset, int length) throws Exception 
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
    SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
    cipher.init(Cipher.ENCRYPT_MODE, key,new IvParameterSpec(IV.getBytes("UTF-8")));
    return cipher.doFinal(plainData, offset, length);
}

应修改decrypt()方法以使用相同的填充。

其他最明显的改进:

  • 为每个数据块生成新的随机IV,并发送包含IV和加密数据的数据包。它需要在发送+加密和接收+解密端的多个字节数组之间进行一些复制,但在同一个密钥上重复使用相同的IV进行多个密码操作绝对是不安全的来自加密点观点。
  • 使用正确的密钥派生函数(搜索PBKDF2),而不是简单地将密码字符串转换为字节。
  • 获取Cipher一次的实例,然后使用密钥和新的IV重新初始化它。这将节省一点CPU和内存。

答案 1 :(得分:0)

@Oleg Estekhin说:“这个问题与UDP或加密无关。你忽略了targetDataLine.read(tempBuffer,0,tempBuffer.length)的返回值。我很确定大多数时候它读取的数量较少比缓冲区大小,其余数据是先前调用的垃圾“  我同意并使用 CTR GCM 作为操作模式 CBC,因为CBC较慢。